Data providers e testes parametrizados
1. Introdução aos Testes Parametrizados
Testes parametrizados são uma técnica poderosa no PHPUnit que permite executar o mesmo método de teste com diferentes conjuntos de dados. Em vez de escrever múltiplos métodos de teste para cada cenário possível, você define um único método que recebe parâmetros e um "data provider" que fornece os valores.
O principal problema que resolvem é a redução de código repetitivo. Imagine testar uma função de validação de CPF com 10 entradas diferentes — sem data providers, você teria 10 métodos quase idênticos. Com eles, tudo se resume a um único método e um array de dados.
A sintaxe básica no PHPUnit envolve a anotação @dataProvider e um método público que retorna um array de arrays.
2. Criando seu Primeiro Data Provider
A estrutura fundamental de um data provider é um método público que retorna um array. Cada elemento do array é outro array contendo os argumentos que serão passados para o método de teste.
<?php
use PHPUnit\Framework\TestCase;
class CpfValidatorTest extends TestCase
{
/**
* @dataProvider cpfProvider
*/
public function testValidarCpf(string $cpf, bool $esperado): void
{
$validador = new CpfValidator();
$this->assertEquals($esperado, $validador->validar($cpf));
}
public function cpfProvider(): array
{
return [
['529.982.247-25', true],
['111.111.111-11', false],
['123.456.789-09', false],
['000.000.000-00', false],
];
}
}
O data provider cpfProvider retorna quatro casos de teste. Cada caso contém um CPF e o resultado esperado. O método testValidarCpf recebe esses valores automaticamente.
3. Formatos de Retorno do Data Provider
O formato mais comum é o array indexado numericamente, como visto acima. No entanto, você pode usar arrays associativos para nomear cada caso:
public function cpfProvider(): array
{
return [
'cpf_valido' => ['529.982.247-25', true],
'cpf_digitos_iguais' => ['111.111.111-11', false],
'cpf_invalido_qualquer' => ['123.456.789-09', false],
];
}
Para conjuntos de dados muito grandes, iterators e geradores com yield são mais eficientes em termos de memória:
public function cpfProvider(): iterable
{
yield 'cpf_valido' => ['529.982.247-25', true];
yield 'cpf_digitos_iguais' => ['111.111.111-11', false];
// ... mais casos
}
4. Trabalhando com Múltiplos Parâmetros
Data providers podem fornecer quantos parâmetros forem necessários. Veja um exemplo com uma calculadora:
<?php
use PHPUnit\Framework\TestCase;
class CalculadoraTest extends TestCase
{
/**
* @dataProvider operacoesProvider
*/
public function testOperacoes(float $a, float $b, string $operacao, float $esperado): void
{
$calc = new Calculadora();
$resultado = $calc->calcular($a, $b, $operacao);
$this->assertEquals($esperado, $resultado);
}
public function operacoesProvider(): array
{
return [
'soma_positivos' => [2, 3, 'soma', 5],
'subtracao' => [10, 4, 'subtracao', 6],
'multiplicacao' => [3, 4, 'multiplicacao', 12],
'divisao' => [10, 2, 'divisao', 5],
'soma_negativos' => [-5, -3, 'soma', -8],
];
}
}
5. Nomeando e Organizando Conjuntos de Dados
Nomear cada caso de teste com chaves associativas traz benefícios significativos. Quando um teste falha, a mensagem de erro inclui o nome do conjunto de dados, facilitando a identificação do problema.
/**
* @dataProvider valoresLimiteProvider
*/
public function testValidacaoIdade(int $idade, bool $esperado): void
{
$validador = new ValidadorIdade();
$this->assertEquals($esperado, $validador->validar($idade));
}
public function valoresLimiteProvider(): array
{
return [
'menor_de_idade' => [17, false],
'exatamente_18' => [18, true],
'adulto_jovem' => [25, true],
'idoso' => [65, true],
'acima_de_120' => [121, false],
];
}
Boas práticas incluem usar nomes descritivos que expliquem o cenário, manter um padrão consistente (snake_case ou camelCase) e evitar nomes genéricos como "caso1", "caso2".
6. Data Providers com Objetos e Dados Complexos
Data providers podem retornar objetos, arrays complexos e instâncias de classes. Isso é útil para testar cenários de criação de entidades:
<?php
use PHPUnit\Framework\TestCase;
class UsuarioFactoryTest extends TestCase
{
/**
* @dataProvider usuarioProvider
*/
public function testCriarUsuario(string $nome, string $email, array $roles, bool $esperado): void
{
$factory = new UsuarioFactory();
$usuario = $factory->criar($nome, $email, $roles);
$this->assertInstanceOf(Usuario::class, $usuario);
$this->assertEquals($esperado, $usuario->isAtivo());
}
public function usuarioProvider(): array
{
$adminRoles = ['ROLE_ADMIN', 'ROLE_USER'];
$userRoles = ['ROLE_USER'];
return [
'usuario_admin' => ['João', 'joao@email.com', $adminRoles, true],
'usuario_comum' => ['Maria', 'maria@email.com', $userRoles, true],
'usuario_sem_roles' => ['Pedro', 'pedro@email.com', [], false],
];
}
}
Para cenários Doctrine com múltiplos atributos:
public function entidadeProvider(): array
{
$enderecoValido = new Endereco('Rua A', '123', 'São Paulo');
$enderecoSemNumero = new Endereco('Rua B', '', 'Rio de Janeiro');
return [
'entidade_completa' => [
new Produto('Notebook', 2500.00, $enderecoValido, true),
true
],
'entidade_sem_endereco' => [
new Produto('Mouse', 50.00, null, false),
false
],
];
}
7. Boas Práticas e Armadilhas Comuns
Isolamento e reutilização: Mantenha data providers em métodos separados ou traits para reutilização entre diferentes classes de teste.
trait UsuarioDataProviderTrait
{
public function usuarioBasicoProvider(): array
{
return [
'usuario_valido' => ['João', 'joao@email.com', '123456'],
'usuario_email_invalido' => ['Maria', 'email-invalido', '123456'],
];
}
}
Evite lógica complexa: Data providers devem ser simples e previsíveis. Lógica condicional ou loops complexos dentro deles dificultam a manutenção e a depuração.
Performance: Para grandes volumes de dados (centenas ou milhares de casos), prefira geradores com yield para evitar consumo excessivo de memória.
PHPUnit 10+: A partir do PHPUnit 10, você pode usar o atributo #[TestWith] para casos inline simples:
#[TestWith([2, 3, 5])]
#[TestWith([0, 0, 0])]
#[TestWith([-1, -1, -2])]
public function testSoma(int $a, int $b, int $esperado): void
{
$this->assertEquals($esperado, $a + $b);
}
8. Integração com Outros Recursos de Teste
Data providers combinam perfeitamente com mocks e stubs. Você pode criar mocks dentro do data provider para cenários específicos:
public function servicoProvider(): array
{
$mockRepositorio = $this->createMock(Repositorio::class);
$mockRepositorio->method('buscar')->willReturn(['dados' => 'valor']);
return [
'com_repositorio_mockado' => [$mockRepositorio, 'entrada', 'esperado'],
];
}
Em testes de integração com banco de dados, data providers podem fornecer diferentes estados iniciais:
/**
* @dataProvider usuarioBancoProvider
*/
public function testBuscarUsuarioPorEmail(string $email, ?array $esperado): void
{
$this->inserirDadosNoBanco($email);
$repositorio = new UsuarioRepository($this->entityManager);
$resultado = $repositorio->buscarPorEmail($email);
if ($esperado === null) {
$this->assertNull($resultado);
} else {
$this->assertNotNull($resultado);
}
}
A combinação com @depends permite criar pipelines de teste onde a saída de um teste alimenta o próximo, enquanto data providers garantem múltiplas entradas:
/**
* @dataProvider entradaProvider
*/
public function testProcessarEntrada(array $dados): array
{
$processador = new Processador();
return $processador->processar($dados);
}
/**
* @depends testProcessarEntrada
*/
public function testValidarSaida(array $resultado): void
{
$this->assertArrayHasKey('status', $resultado);
$this->assertEquals('sucesso', $resultado['status']);
}
Referências
- PHPUnit Documentation: Data Providers — Documentação oficial do PHPUnit sobre data providers, com exemplos detalhados e sintaxe completa.
- PHPUnit: Testing with Data Providers — Guia em português sobre como escrever testes com data providers no PHPUnit.
- Laravel Testing: Data Providers — Documentação do Laravel sobre como usar data providers em testes de aplicações Laravel.
- Symfony PHPUnit Data Providers — Guia oficial do Symfony sobre testes parametrizados e data providers no ecossistema Symfony.
- PHPUnit Data Providers with Yield — Artigo da PHP Architect sobre o uso de geradores (yield) em data providers para economia de memória.
- Testing with Data Providers in PHPUnit — Tutorial do SitePoint sobre data providers, cobrindo desde o básico até exemplos avançados com objetos e arrays complexos.