PHP

Guia Completo de PHPUnit na Prática: Testes Unitários em PHP

9 min de leitura

Guia Completo de PHPUnit na Prática: Testes Unitários em PHP

O que é PHPUnit e Por Que Usar PHPUnit é o framework de testes unitários mais consolidado do ecossistema PHP. Ele permite escrever testes automatizados que validam o comportamento do seu código em isolamento, identificando bugs antes que cheguem à produção. A filosofia é simples: cada teste deve verificar uma única funcionalidade, ser rápido de executar e não depender de outros testes. Usar PHPUnit traz benefícios concretos: maior confiança ao refatorar código, documentação viva do comportamento esperado, e redução de bugs em produção. Empresas sérias exigem testes unitários como parte do padrão de qualidade. Se você quer se destacar como desenvolvedor PHP profissional, dominar testes é essencial. Instalação e Configuração Inicial A forma mais prática de instalar PHPUnit é via Composer. Execute: Crie um arquivo na raiz do seu projeto para configurar o ambiente: Crie a estrutura de pastas: para o código principal e para os testes. Execute para rodar os testes — é isso, você está pronto para começar.

<h2>O que é PHPUnit e Por Que Usar</h2>

<p>PHPUnit é o framework de testes unitários mais consolidado do ecossistema PHP. Ele permite escrever testes automatizados que validam o comportamento do seu código em isolamento, identificando bugs antes que cheguem à produção. A filosofia é simples: cada teste deve verificar uma única funcionalidade, ser rápido de executar e não depender de outros testes.</p>

<p>Usar PHPUnit traz benefícios concretos: maior confiança ao refatorar código, documentação viva do comportamento esperado, e redução de bugs em produção. Empresas sérias exigem testes unitários como parte do padrão de qualidade. Se você quer se destacar como desenvolvedor PHP profissional, dominar testes é essencial.</p>

<h2>Instalação e Configuração Inicial</h2>

<p>A forma mais prática de instalar PHPUnit é via Composer. Execute:</p>

<pre><code class="language-bash">composer require --dev phpunit/phpunit ^10</code></pre>

<p>Crie um arquivo <code>phpunit.xml</code> na raiz do seu projeto para configurar o ambiente:</p>

<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;

&lt;phpunit xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;

xsi:noNamespaceSchemaLocation=&quot;https://schema.phpunit.de/10.5/phpunit.xsd&quot;

bootstrap=&quot;tests/bootstrap.php&quot;

cacheDirectory=&quot;.phpunit.cache&quot;

colors=&quot;true&quot;&gt;

&lt;testsuites&gt;

&lt;testsuite name=&quot;Unit&quot;&gt;

&lt;directory&gt;tests/Unit&lt;/directory&gt;

&lt;/testsuite&gt;

&lt;/testsuites&gt;

&lt;source&gt;

&lt;include&gt;

&lt;directory&gt;src&lt;/directory&gt;

&lt;/include&gt;

&lt;/source&gt;

&lt;/phpunit&gt;</code></pre>

<p>Crie a estrutura de pastas: <code>src/</code> para o código principal e <code>tests/Unit/</code> para os testes. Execute <code>vendor/bin/phpunit</code> para rodar os testes — é isso, você está pronto para começar.</p>

<h2>Escrevendo Seu Primeiro Teste</h2>

<h3>Exemplo Prático: Testando uma Calculadora</h3>

<p>Criaremos uma classe <code>Calculadora</code> simples e seus testes. Primeiro, a classe em <code>src/Calculadora.php</code>:</p>

<pre><code class="language-php">&lt;?php

namespace App;

class Calculadora

{

public function somar(float $a, float $b): float

{

return $a + $b;

}

public function dividir(float $a, float $b): float

{

if ($b === 0) {

throw new \InvalidArgumentException(&#039;Divisor não pode ser zero&#039;);

}

return $a / $b;

}

}</code></pre>

<p>Agora o teste em <code>tests/Unit/CalculadoraTest.php</code>:</p>

<pre><code class="language-php">&lt;?php

namespace Tests\Unit;

use App\Calculadora;

use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase

{

private Calculadora $calc;

protected function setUp(): void

{

$this-&gt;calc = new Calculadora();

}

public function test_soma_dois_numeros_positivos(): void

{

$resultado = $this-&gt;calc-&gt;somar(5, 3);

$this-&gt;assertEquals(8, $resultado);

}

public function test_soma_com_numeros_negativos(): void

{

$resultado = $this-&gt;calc-&gt;somar(-5, 3);

$this-&gt;assertEquals(-2, $resultado);

}

public function test_divisao_por_zero_lanca_excecao(): void

{

$this-&gt;expectException(\InvalidArgumentException::class);

$this-&gt;calc-&gt;dividir(10, 0);

}

public function test_divisao_valida(): void

{

$resultado = $this-&gt;calc-&gt;dividir(10, 2);

$this-&gt;assertEquals(5.0, $resultado);

}

}</code></pre>

<p>Execute <code>vendor/bin/phpunit tests/Unit/CalculadoraTest.php</code>. Todos os testes devem passar. Observe a estrutura: cada método <code>test_*</code> é um teste isolado. O método <code>setUp()</code> é executado antes de cada teste, permitindo reutilizar o objeto <code>$this-&gt;calc</code>.</p>

<h3>Assertions Comuns</h3>

<p>As asserções são o coração do teste — elas verificam se o resultado está correto:</p>

<pre><code class="language-php">$this-&gt;assertEquals($esperado, $atual); // Igualdade

$this-&gt;assertTrue($condicao); // Verdadeiro

$this-&gt;assertFalse($condicao); // Falso

$this-&gt;assertNull($valor); // Nulo

$this-&gt;assertContains($item, $array); // Item em array

$this-&gt;assertCount(3, $array); // Quantidade

$this-&gt;assertThrows(Exception::class, $callable); // Exceção</code></pre>

<h2>Testes Avançados: Mocks e Stubs</h2>

<h3>Isolando Dependências com Mocks</h3>

<p>Código real frequentemente depende de outros objetos. Para testar isoladamente, usamos mocks — objetos falsos que simulam comportamento. Imagine uma classe <code>UsuarioService</code> que chama um banco de dados:</p>

<pre><code class="language-php">&lt;?php

namespace App;

interface UsuarioRepositorio

{

public function buscarPorEmail(string $email): ?array;

}

class UsuarioService

{

public function __construct(private UsuarioRepositorio $repo) {}

public function autenticar(string $email, string $senha): bool

{

$usuario = $this-&gt;repo-&gt;buscarPorEmail($email);

return $usuario &amp;&amp; password_verify($senha, $usuario[&#039;senha&#039;]);

}

}</code></pre>

<p>Teste com mock em <code>tests/Unit/UsuarioServiceTest.php</code>:</p>

<pre><code class="language-php">&lt;?php

namespace Tests\Unit;

use App\UsuarioService;

use App\UsuarioRepositorio;

use PHPUnit\Framework\TestCase;

class UsuarioServiceTest extends TestCase

{

public function test_autenticacao_com_credenciais_validas(): void

{

// Criar mock do repositório

$mockRepo = $this-&gt;createMock(UsuarioRepositorio::class);

// Simular retorno do método

$mockRepo-&gt;expects($this-&gt;once())

-&gt;method(&#039;buscarPorEmail&#039;)

-&gt;with(&#039;joao@email.com&#039;)

-&gt;willReturn([

&#039;email&#039; =&gt; &#039;joao@email.com&#039;,

&#039;senha&#039; =&gt; password_hash(&#039;senha123&#039;, PASSWORD_BCRYPT)

]);

$service = new UsuarioService($mockRepo);

$resultado = $service-&gt;autenticar(&#039;joao@email.com&#039;, &#039;senha123&#039;);

$this-&gt;assertTrue($resultado);

}

public function test_autenticacao_falha_com_email_inexistente(): void

{

$mockRepo = $this-&gt;createMock(UsuarioRepositorio::class);

$mockRepo-&gt;expects($this-&gt;once())

-&gt;method(&#039;buscarPorEmail&#039;)

-&gt;willReturn(null);

$service = new UsuarioService($mockRepo);

$resultado = $service-&gt;autenticar(&#039;inexistente@email.com&#039;, &#039;qualquer&#039;);

$this-&gt;assertFalse($resultado);

}

}</code></pre>

<p>O mock simula o repositório sem acessar o banco de dados real. <code>expects($this-&gt;once())</code> valida que o método foi chamado exatamente uma vez. <code>willReturn()</code> define o retorno simulado. Isso garante que seu teste é rápido, isolado e confiável.</p>

<h2>Boas Práticas e Padrões</h2>

<h3>O Padrão AAA</h3>

<p>Todo bom teste segue o padrão <strong>Arrange-Act-Assert</strong> (Preparar-Agir-Verificar):</p>

<pre><code class="language-php">public function test_desconto_aplicado_corretamente(): void

{

// Arrange: preparar dados

$preco = 100.0;

$percentualDesconto = 10;

// Act: executar a ação

$precoFinal = aplicarDesconto($preco, $percentualDesconto);

// Assert: verificar resultado

$this-&gt;assertEquals(90.0, $precoFinal);

}</code></pre>

<h3>Nomes Descritivos</h3>

<p>Evite nomes genéricos como <code>test_ok()</code>. Use <code>test_calcula_juros_corretamente()</code> — o nome documenta o comportamento esperado.</p>

<blockquote><p><strong>Dica profissional:</strong> Uma test suite bem nomeada serve como documentação viva. Qualquer desenvolvedor deve entender o que cada teste valida apenas lendo os nomes dos métodos.</p></blockquote>

<h3>Cobertura de Testes</h3>

<p>Verifique quantas linhas do seu código estão sendo testadas:</p>

<pre><code class="language-bash">vendor/bin/phpunit --coverage-html coverage/</code></pre>

<p>Abra <code>coverage/index.html</code> no navegador. Almeje 80-90% de cobertura — 100% é impraticável e nem sempre necessário.</p>

<h2>Conclusão</h2>

<p>PHPUnit transforma desenvolvimento PHP de um processo arriscado em uma prática profissional confiável. Comece com testes simples usando <code>assertEquals()</code> e <code>assertTrue()</code>, evoluindo para mocks quando suas dependências crescerem. Lembre-se: testes devem ser rápidos, isolados e escritos <strong>antes</strong> ou <strong>junto com</strong> o código (TDD). A consistência importa mais que a perfeição — um teste simples e confiável vale mais que uma test suite complexa e flaky. Invista nessa habilidade agora e colha benefícios por toda sua carreira.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://phpunit.de/documentation.html" target="_blank" rel="noopener noreferrer">Documentação Oficial PHPUnit</a></li>

<li><a href="https://www.php.net/manual/pt_BR/" target="_blank" rel="noopener noreferrer">PHP: Testes Unitários com PHPUnit</a></li>

<li><a href="https://www.oreilly.com/library/view/test-driven-development/0201616416/" target="_blank" rel="noopener noreferrer">Test Driven Development: By Example - Kent Beck</a></li>

<li><a href="https://www.oreilly.com/library/view/working-effectively-with/0131177052/" target="_blank" rel="noopener noreferrer">Working Effectively with Legacy Code - Michael Feathers</a></li>

<li><a href="https://www.php-fig.org/psr/psr-12/" target="_blank" rel="noopener noreferrer">PSR-12: Extended Coding Style Guide</a></li>

</ul>

Comentários

Mais em PHP

Migrations Manuais em PHP: Versionando o Banco de Dados na Prática
Migrations Manuais em PHP: Versionando o Banco de Dados na Prática

O que são Migrations e por que você precisa delas Migrations são controle de...

O que Todo Dev Deve Saber sobre Strings em PHP: Manipulação, Funções e Formatação
O que Todo Dev Deve Saber sobre Strings em PHP: Manipulação, Funções e Formatação

Fundamentos de Strings em PHP Uma string em PHP é uma sequência de caracteres...

Dominando Formulários HTML e Manipulação de Dados com PHP em Projetos Reais
Dominando Formulários HTML e Manipulação de Dados com PHP em Projetos Reais

Fundamentos de Formulários HTML Um formulário HTML é a porta de entrada para...