Testar software é uma prática essencial no desenvolvimento moderno, e no PHP não é diferente. Escrever testes automatizados ajuda a garantir que o código funcione como esperado, facilita a manutenção e previne regressões. Nesta aula, vamos explorar os motivos para testar, os tipos de testes existentes, conhecer o PHPUnit – a principal ferramenta de testes para PHP – e escrever nosso primeiro teste. Ao final, você terá uma base sólida para começar a implementar testes em seus projetos.

Muitos desenvolvedores iniciantes negligenciam os testes por acharem que consomem tempo, mas na prática eles economizam horas de depuração e trazem confiança para refatorar e adicionar novas funcionalidades. Vamos desmistificar o processo e mostrar que testar pode ser simples e gratificante.

Por que testar

Testar não é apenas uma formalidade; é uma forma de validar que o código atende aos requisitos e continua funcionando ao longo do tempo. Quando você escreve testes, está documentando o comportamento esperado do sistema, o que auxilia novos desenvolvedores a entenderem o código. Além disso, testes automatizados permitem detectar erros imediatamente após uma alteração, evitando que bugs cheguem à produção.

Sem testes, qualquer mudança em uma parte do sistema pode quebrar funcionalidades em outros lugares sem que ninguém perceba até que um usuário final reclame. Isso gera retrabalho e aumenta o custo de manutenção. Com testes, você pode refatorar com segurança, sabendo que se algo quebrar, o teste falhará e indicará o problema. Outro ponto importante: testes incentivam um design de código mais limpo e modular, pois para testar bem, você precisa escrever funções e classes com responsabilidades bem definidas.

Benefícios principais:

  • Redução de bugs: testes capturam erros antes da publicação.
  • Confiança para mudanças: permite alterar código sem medo de quebrar algo.
  • Documentação viva: testes mostram como a função deve ser usada.
  • Economia de tempo: embora exijam investimento inicial, reduzem o tempo de depuração.
  • Qualidade do código: incentiva boas práticas e separação de responsabilidades.

Tipos de teste

Existem vários níveis de teste, cada um com um objetivo específico. Os mais comuns no desenvolvimento de software são:

  • Testes unitários: verificam uma unidade isolada de código, como uma função ou método. São rápidos e focados.
  • Testes de integração: garantem que diferentes módulos ou serviços funcionem juntos corretamente, por exemplo, interação com banco de dados.
  • Testes funcionais: validam o comportamento do sistema de ponta a ponta, simulando ações do usuário.
  • Testes de aceitação: definem critérios de aceitação do cliente, geralmente automatizados com ferramentas como Behat.
  • Testes de performance: avaliam a velocidade e a escalabilidade do sistema.

No contexto de PHP, os testes unitários são os mais difundidos e o PHPUnit é a ferramenta padrão para escrevê-los. Testes de integração também são comuns, especialmente quando se utiliza bancos de dados ou APIs externas. Nesta aula, focaremos nos testes unitários, pois eles formam a base de qualquer estratégia de testes.

PHPUnit (visão geral)

PHPUnit é um framework de testes para PHP, inspirado no JUnit do Java. Ele permite criar testes que verificam se o código produz os resultados esperados. É altamente configurável e se integra a ferramentas de CI/CD, como GitHub Actions ou Jenkins. A versão mais recente (PHPUnit 10 ou 11) requer PHP 8.1 ou superior e traz melhorias de performance e nova API de atributos.

Para instalar, usamos o Composer, o gerenciador de dependências do PHP. O comando típico é:

composer require --dev phpunit/phpunit

Após a instalação, você pode executar os testes com vendor/bin/phpunit. O PHPUnit procura por arquivos de teste em um diretório específico (por padrão, tests) e executa métodos que começam com test ou que possuem o atributo #[Test]. Cada método de teste deve fazer uma ou mais asserções usando métodos como assertEquals, assertTrue, assertFalse, entre outros.

Uma classe de teste normalmente estende a classe PHPUnit\Framework\TestCase. Dentro dela, você escreve métodos públicos que representam os testes. O PHPUnit fornece também métodos de ciclo de vida: setUp() e tearDown() para preparar e limpar o ambiente antes e depois de cada teste.

Exemplo básico de estrutura:

use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase
{
    public function testSoma(): void
    {
        $calc = new Calculadora();
        $this->assertEquals(4, $calc->soma(2, 2));
    }
}

Primeiro teste

Vamos colocar em prática tudo o que vimos. Suponha que temos uma classe Calculadora com um método soma. Primeiro, criamos um projeto com Composer e instalamos o PHPUnit. Em seguida, criamos a classe no diretório src e o teste no diretório tests.

Passo a passo:

  1. Inicie um projeto: composer init e depois composer require --dev phpunit/phpunit.
  2. Crie o arquivo src/Calculadora.php com a classe.
  3. Crie o arquivo tests/CalculadoraTest.php com o teste.
  4. Configure o composer.json para o autoload de classes (PSR-4).
  5. Execute os testes com vendor/bin/phpunit.

Aqui está o código completo:

<?php
// src/Calculadora.php
class Calculadora {
    public function soma(float $a, float $b): float {
        return $a + $b;
    }
}
?>

Teste:

<?php
use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase {
    public function testSoma(): void {
        $calc = new Calculadora();
        $this->assertEquals(4, $calc->soma(2, 2));
    }
}
?>

Para executar, no terminal digite vendor/bin/phpunit. Se tudo estiver correto, você verá uma saída com um ponto verde e a mensagem OK (1 test, 1 assertion). Ao adicionar mais testes, o PHPUnit mostrará quantos passaram e quantos falharam, com detalhes sobre as falhas.

É importante que os testes sejam independentes e não dependam de ordem de execução. Cada teste deve validar um comportamento específico. No exemplo acima, testamos apenas a soma de números positivos. Seria interessante adicionar testes para números negativos, zeros, e até mesmo para tipos inválidos, se aplicável.

Nesta aula, você aprendeu a importância dos testes, os tipos existentes, conheceu o PHPUnit e criou seu primeiro teste. Agora, pratique com os exercícios abaixo.

Boas práticas

  • Escreva testes pequenos e focados em um único comportamento.
  • Use nomes descritivos nos métodos de teste, como testSomaDeNumerosPositivos.
  • Mantenha os testes independentes e executáveis em qualquer ordem.
  • Rode os testes com frequência, de preferência a cada alteração no código.
  • Não teste apenas os caminhos felizes; inclua casos de erro e extremos.

Exercícios

  1. Crie uma classe Subtracao com um método subtrair que retorna a diferença entre dois números. Escreva um teste que verifica a subtração de 5 e 3, esperando 2.
  2. Adicione um teste para a Calculadora que verifica a soma de números negativos: -2 + -3 = -5.
  3. Escreva um teste que falhe de propósito (por exemplo, esperando 5 para a soma de 2 e 2) e execute-o. Observe a mensagem de falha.
  4. Modifique a classe Calculadora para que o método soma lance uma exceção se algum argumento não for numérico. Escreva um teste que verifica se a exceção é lançada.
  5. Utilize o método setUp() para instanciar a calculadora uma única vez e reutilize-a nos testes de soma e subtração.

✓ Resposta:
// src/Subtracao.php
class Subtracao {
    public function subtrair(float $a, float $b): float {
        return $a - $b;
    }
}

// tests/SubtracaoTest.php
use PHPUnit\Framework\TestCase;

class SubtracaoTest extends TestCase {
    public function testSubtrai(): void {
        $sub = new Subtracao();
        $this->assertEquals(2, $sub->subtrair(5, 3));
    }
}

✓ Resposta:
// Adicione em CalculadoraTest
public function testSomaNegativos(): void {
    $calc = new Calculadora();
    $this->assertEquals(-5, $calc->soma(-2, -3));
}

✓ Resposta:
// Adicione em CalculadoraTest
public function testSomaFalha(): void {
    $calc = new Calculadora();
    $this->assertEquals(5, $calc->soma(2, 2)); // Falha: esperado 5, obtido 4
}

✓ Resposta:
// src/Calculadora.php modificado
class Calculadora {
    public function soma($a, $b): float {
        if (!is_numeric($a) || !is_numeric($b)) {
            throw new InvalidArgumentException('Argumentos devem ser numéricos');
        }
        return $a + $b;
    }
}

// tests/CalculadoraTest.php
public function testSomaComStrings(): void {
    $this->expectException(InvalidArgumentException::class);
    $calc = new Calculadora();
    $calc->soma('a', 2);
}

✓ Resposta:
// tests/CalculadoraTest.php
use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase {
    private $calc;

    protected function setUp(): void {
        $this->calc = new Calculadora();
    }

    public function testSoma(): void {
        $this->assertEquals(4, $this->calc->soma(2, 2));
    }

    public function testSubtrai(): void {
        $sub = new Subtracao(); // se disponível
        $this->assertEquals(2, $sub->subtrair(5, 3));
    }
}

Referências