Introdução aos testes com PHPUnit
Parte I
Já faz algum tempo que venho adiando uma série sobre testes em forma de vídeo, como eu penso que os vídeos trazem uma maior complexidade para desenvolver em relação ao texto resolvi escrever o que pode (ou não) se torna uma série de vídeos no YouTube, se isso acontecer vou atualizar esse texto com o vídeo.
Nessa série eu pretendo apresentar os principais conceitos de testes, vamos ver porque métodos estáticos são ruins para testes, como criar dublês de testes, injeção de dependência e espero que você ame testes após ler isso <3
Não vamos falar sobre TDD, mas você ira ver que escrever testes pode mudar a sua maneira de pensar e escrever software.
Antes de começamos
Estou assumindo que você tem um ambiente PHP já configurado, recomendo fortemente que use um container docker com PHP 8.1 assim essa série terá uma vida maior no que se refere ao código (versão do PHP), embora o conceito sobreviva com o passar dos anos. Também vamos usar majoritariamente a linha de comando, se você não estiver familiarizado com isso aqui tem uma artigo com oo principais pontos sobre isso.
Instalando o PHPUnit
Para gerenciar todas as dependências usaremos o composer, se você não conhece o composer recomendo ler esse artigo. Vamos usar o comando abaixo no diretório do nosso projeto.
composer require --dev phpunit/phpunit
Após executarmos esse comando teremos um arquivo composer.json na raiz do projeto com um conteúdo parecido com o que temos abaixo
{
"require-dev": {
"phpunit/phpunit": "^9.5"
}
}
Vamos usar o seguinte comando para termos certeza que tudo funcionou sem erros.
./vendor/bin/phpunit --version
A saída deve ser algo parecido com PHPUnit 9.5.20 #StandWithUkraine Agora vamos abrir o arquivo composer.json e altera-lo para termos algo semelhante ao exemplo abaixo.
{
"require": {
},
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
Explicando o arquivo
- temos a chave require vazia que é onde ficam as dependências do projeto
- a require-dev são as dependências de desenvolvimento e por isso o PHPUnit está ali.
- autoload utilizamos a PSR-4 que define as especificações do autoloader, ali temos o namespace que vai apontar para a pasta src, da mesma forma temos o autoload-dev
Vamos agora personalizar as configurações do PHPUnit, você pode rodar ele com a configuração padrão assim como personalizar essas configurações, para isso vamos criar o arquivo phpunit.xml,
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
verbose="true"
stopOnFailure="false">
<testsuites>
<testsuite name="Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>
- directory informa para o PHPUnit onde estão os testes que vamos rodar
- colors estamos especificando que queremos uma saída com as cores ativadas para facilitar a visualização no terminar
- verbose é ativado para termos mais informações sobre os erros
- stopOnFailure desativado para os testes não pararem no primeiro erro encontrado. Também já definimos o coverage e mais a frente explico o porquê de utilizarmos.
Vamos rodar mais uma vez o comando ./vendor/bin/phpunit para ver se deu tudo certo, então teremos a saída semelhante a essa
PHPUnit 9.5.20 #StandWithUkraine
Runtime: PHP 8.1.4
Configuration: /var/www/html/phpunit.xml
Convenções
O PHPUnit tem algumas convenções que embora você não precise segui-las eu acho que é altamente recomendável que você as sigas, por isso abaixo vou falar sobre algumas. Estrutura de pastas e arquivos
No caso desse exemplo os arquivos vão ficar dentro de src, portanto se você tiver um arquivo no endereço src/Domain/Entities/User.php o seu teste deve ficar em tests/Domain/Entities/UserTest.php e a sua classe de teste deve ter o mesmo nome que o arquivo ou seja class UserTest{}
Nome de métodos
Dentro das classes de testes os métodos devem seguir a convenção para que sejam executados pela suite de testes, o método deve ser público e o nome dele deve iniciar com test, em minúsculo, ou ter a annotation @test.
Devemos escrever como nome do método, um nome que faça sentido para o teste informando o nome do método que deve ser testado, não há espaço para nomes de métodos curtos, verbosidade é que o que você deve buscar ao escrever nome dos métodos porque você precisará saber ao que se refere o teste quando ele falha e ele irá falhar.
Por fim suas classes devem estender a classe PHPUnit_Framework_TestCase ou outra classe equivalente.
Finalmente nosso primeiro teste
Vamos criar uma classe calculadora e um método que faz a soma de números, nossa classe te teste ficaria assim
<?php
use App\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function test_it_should_do_a_sum_and_return_the_result()
{
$sum = new Calculator();
$result = $sum->sum(10,8.5);
$this->assertEquals(18.5, $result);
}
}
Vamos salvar essa classe na raiz do diretório tests, definimos que nossa classe se chama CalculatorTest (assim como o arquivo) e estende o TestCase do PHPUnit. Nosso método inicia como test e depois temos um nome utilizando snake_case, algumas pessoas preferem usar CamelCase, isso é mais definição de legibilidade e padrão que o time quer seguir. Uma outra opção para definirmos a classe é utilizar annotations e desse forma podemos subtrair o test no início do nome do método, como no exemplo abaixo, ambos os exemplos funcionam da mesma maneira.
<?php
use App\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
/**
* @return void
* @test
*/
public function it_should_do_a_sum_and_return_the_result()
{
$sum = new Calculator();
$result = $sum->sum(10,8.5);
$this->assertEquals(18.5, $result);
}
}
Agora vamos escrever a classe que faz o cálculo dentro do diretório src/
<?php
namespace App;
class Calculator
{
public function Sum(int|float ... $numbers): float|int
{
$sum = 0;
foreach($numbers as $number){
$sum += $number;
}
return $sum;
}
}
O que estamos fazendo é definindo uma classe Calculator e um método público Sum, nesse método podemos receber vários números que devem ser inteiro ou ponto flutuante, o retorno também pode ser inteiro ou ponto flutuante, dentro do métodos apenas um loop que vai somando cada número e um retorno. O código é simples mas a ideia é que ao invés de instanciarmos essa classe, ir no navegador e ver um print vamos utilizar uma automação e assim sempre que fizermos alterações testamos para ver se tudo continua funcionando da mesma maneira.
Rodando o comando ./vendor/bin/phpunit temos a "barra verde" indicando que nossos testes passaram.

descrição da imagem: tela preta do shell do linux com a saída do comando ./vendo/bin/phpunit na imagem temos 5 linhas, onde na primeira temos a versão do PHP que é a 8.1.4, na segunda linha indica o endereço no computador das configurações do PHPUnit, na terceira linha temos um ponto na esquerda e na direita temos o texto 1/1 (100%), na quarta linha o tempo de execução e a quantidade de memória usada, na quinta e última linha temos o texto com fundo verde OK(1 test, 1 assertion), indicando que todos os testes passaram.
Nota: Sei que poderia ter usado imagens nos códigos o que ficaria visualmente mais bonito para quem enxerga mas menos acessível, por isso escolhi a acessibilidade embora que não tenha conhecimento sobre, estou acreditando que isso seria melhor do que termos várias imagens de código.
Então chegamos ao fim da primeira parte, agradeço a quem chegou até o fim e qualquer erro ou sugestão podem me chamar que terei prazer em corrigir.
