Testes em PHP: Unit, Integration, E2E em nível avançado
No desenvolvimento web moderno em PHP, a qualidade do código deixou de ser uma opção — é uma condição obrigatória para a sobrevivência do projeto. Testes unitários básicos são familiares a todo desenvolvedor, mas assim que o projeto ultrapassa os limites do "Hello World", surge a necessidade de abordagens mais complexas: testes de integração, verificação da interação entre componentes e testes ponta a ponta (E2E) de cenários de usuário.
Este artigo é destinado a desenvolvedores PHP de nível Middle e superior, que já conhecem o PHPUnit e desejam aprofundar seus conhecimentos. Abordaremos técnicas avançadas: testes com dependências externas, trabalho com contêineres de teste, isolamento de infraestrutura e construção de uma estratégia de testes completa.
1. Testes Unitários Avançados: Objetos Mock e Stubs
Em nível avançado, os testes unitários deixam de ser apenas a verificação de funções matemáticas. Eles testam a lógica de negócios, isolada do mundo externo. As ferramentas-chave são Mock e Stub.
Stub retorna dados predefinidos. Mock verifica como exatamente os métodos foram chamados (número de chamadas, ordem dos argumentos).
Vamos considerar um exemplo: um serviço de pagamento que envia uma requisição para um gateway de pagamento.
<?phpclass PaymentService { public function __construct( private PaymentGatewayInterface $gateway, private LoggerInterface $logger ) {}
public function processPayment(Order $order): bool { $result = $this->gateway->charge( $order->getAmount(), $order->getCurrency() );
if ($result->isSuccess()) { $this->logger->info('Payment processed', ['order_id' => $order->getId()]); return true; }
$this->logger->warning('Payment failed', ['error' => $result->getError()]); return false; }}
// Teste usando PHPUnitclass PaymentServiceTest extends TestCase { public function testSuccessfulPaymentLogsInfo(): void { // Criando um Stub para o gateway $gatewayStub = $this->createStub(PaymentGatewayInterface::class); $gatewayStub->method('charge') ->willReturn(new PaymentResult(true));
// Criando um Mock para o logger (verificando a chamada) $loggerMock = $this->createMock(LoggerInterface::class); $loggerMock->expects($this->once()) ->method('info') ->with('Payment processed', $this->arrayHasKey('order_id'));
$service = new PaymentService($gatewayStub, $loggerMock); $result = $service->processPayment(new Order(100, 'USD'));
$this->assertTrue($result); }}Técnica avançada: use willReturnCallback() para respostas dinâmicas e withConsecutive() para verificar a sequência de chamadas.
2. Testes de Integração: Banco de Dados e APIs Externas
Os testes de integração verificam como seu código interage com componentes reais: banco de dados, sistema de arquivos, cache. Aqui não se pode confiar em mocks — é necessária uma infraestrutura real.
Problema: os testes se tornam lentos e frágeis. Solução: usar contêineres de teste (Testcontainers) ou bancos de dados SQLite embutidos.
Exemplo de teste de integração para um repositório de usuários com Doctrine:
<?phpclass UserRepositoryIntegrationTest extends KernelTestCase { private EntityManagerInterface $entityManager; private UserRepository $repository;
protected function setUp(): void { self::bootKernel(); $this->entityManager = self::$kernel->getContainer() ->get('doctrine