Como adicionar testes unitários e de integração em código legado PHP sem quebrar produção
Por Kevin Marcondes · Publicado em · Atualizado em · 8 min de leitura
Você precisa saber como adicionar testes unitários e de integração em código legado PHP sem quebrar produção. Este guia mostra uma abordagem segura e incremental. Do diagnóstico à automação, você verá como ganhar confiança no legado, isolar dependências e criar uma base de testes que impede regressões, sem interrupções indesejadas no ambiente produtivo.
Mapeie o legado e os riscos antes do primeiro teste
Comece entendendo onde o código toca recursos externos. Banco de dados, arquivos, e serviços HTTP são fontes comuns de efeitos colaterais. Liste pontos de entrada, jobs e comandos de cron que rodam em produção.
Levante dependências e versões. Verifique composer.json, extensões do PHP e integrações. Descobrir versões antigas evita investir tempo em soluções incompatíveis.
Identifique acoplamentos fortes. Singletons, funções globais e chamadas estáticas difíceis de simular exigem “seams” de teste. Um seam é um ponto onde você injeta comportamento alternativo em testes.
Defina escopo mínimo viável. Seu primeiro objetivo é rodar um teste sem tocar produção. Restrinja o alvo a módulos de baixo risco. Ao longo do processo, considere apoio de manutenção contínua, como descrito em manutenção de software.
Estratégia em camadas: do unitário ao smoke de integração
Use uma pirâmide de testes. Muitos testes unitários rápidos, alguns de integração confiáveis e poucos testes de ponta a ponta. Em legado, a pirâmide reduz custo e acelera feedback.
Comece por unitários nas regras de negócio, depois integração em pontos críticos. Por fim, smoke tests validam o fluxo essencial sem cobrir todos os cenários.
A tabela resume como cada tipo ajuda no legado:
| Tipo | Escopo | Velocidade | Isolamento | Quando usar |
|---|---|---|---|---|
| Unitário | Funções/métodos isolados | Muito alta | Alta | Regras de negócio e cálculos sem I/O |
| Integração | Módulos com DB/HTTP/FS | Média | Média | Persistência, repositórios, gateways |
| Contrato/API | Interfaces entre serviços | Média | Média | Garantir formatos e compatibilidade |
| Smoke (E2E leve) | Fluxos essenciais | Baixa | Baixa | Verificar que o básico funciona |
Ambiente seguro: branches, CI e dados de teste controlados
Trabalhe em uma branch dedicada. Configure integração contínua (CI) para rodar testes em cada commit. O objetivo é falhar cedo, longe da produção.
Separe configurações por ambiente. Use arquivos .env e um perfil específico de testes (.env.testing). Nunca use credenciais reais nos testes.
Para banco de dados, prefira uma instância isolada. Use transações com rollback para manter o estado limpo entre testes. Veja a documentação de transações em PDO.
Crie dados determinísticos com fixtures ou factories. Isso torna os testes reprodutíveis. Em aplicações Laravel, o teste de banco é facilitado pelo framework; veja Database Testing. Além disso, para gestão de testes como produto, confira o serviço de software de teste.
- Ative logs detalhados no ambiente de teste.
- Bloqueie saídas de e-mail e SMS ao testar.
- Use variáveis de ambiente para endpoints falsos.
- Rode testes em containers ou VMs descartáveis.
Isolando dependências difíceis no código legado
Crie seams para quebrar acoplamentos. Encapsule chamadas estáticas e funções globais em adaptadores. Injete esses adaptadores nas classes que você deseja testar.
Para chamadas de tempo e I/O, use wrappers. Em vez de time() diretamente, injete um Clock. Em vez de file_get_contents, injete um FileReader. Isso permite mocks nos testes.
Use mocks e stubs para simular dependências. No PHPUnit, crie dublês para interfaces e classes injetadas. Dublês permitem focar na lógica, não no mundo externo.
Quando há singletons, introduza uma fábrica injetável. Mantenha a API pública, mas delegue a criação para um provedor configurável nos testes.
- Priorize pontos com maior acoplamento e maior risco.
- Introduza interfaces mínimas, sem grande refatoração.
- Extraia funções puras para módulos separados.
- Evite alterar a semântica do código em refactors iniciais.
Primeiros testes unitários: comece pequeno e gere confiança
Escolha unidades sem efeitos colaterais. Regras de negócio e validações são bons candidatos. Comece por funções com entradas claras e saídas previsíveis.
Crie testes de caracterização onde a lógica é obscura. Congele o comportamento atual em testes. Em seguida, refatore com segurança, mantendo a especificação viva.
Transforme bugs em testes. Toda correção ganha um caso que falha antes e passa depois. Isso protege contra regressões futuras.
- Mapeie 5 funções puras e escreva casos felizes e de borda.
- Use dados pequenos e legíveis nos asserts.
- Evite cenários múltiplos em um único teste.
- Nomeie testes pelo comportamento esperado.
Testes de integração: DB, HTTP e arquivos sem tocar produção
Para o banco, rode em instância separada. Use transações e rollback por teste. Em alguns cenários, usar SQLite em memória acelera ciclos, se compatível com o ORM.
Para HTTP, substitua clientes reais por handlers falsos. No Guzzle, um mock handler simula respostas. Integrações externas reais devem ir para staging, nunca produção.
Para arquivos, prefira diretórios temporários e wrappers. Streams como php://memory e php://temp evitam gravar no disco real. Revise os wrappers suportados em stream wrappers.
Controle ordens de migração e seeds. Recrie o schema antes da suíte e isole estados com transações. Se precisar de apoio tático, veja a nossa consultoria de software.
- Separe credenciais e endpoints de teste.
- Use timeouts curtos para falhas rápidas.
- Marque testes lentos e rode-os em jobs dedicados.
- Agende smoke tests após deploy em staging.
Automação e qualidade contínua no legado
Defina políticas de CI. Todo novo código entra com testes. Alterou lógica? Adicione ou ajuste casos. Quebra de teste impede merge até a correção.
Inclua relatórios úteis, sem obsessão por porcentagem. Busque cobertura de risco, não número absoluto. Priorize módulos críticos ao negócio.
Implemente hooks locais. Um pre-commit roda linters e a suíte rápida de testes. O pipeline completo roda no servidor de CI, com cache para acelerar.
Documente como rodar testes localmente e na CI. Ensine como semear dados e como depurar falhas. Isso reduz atrito entre times e aumenta adesão.
- Gates por caminho crítico do deploy.
- Smoke em staging pós-migração.
- Alertas no chat para falhas na main.
- Checklist de rollback por feature.
Caminho incremental e quando buscar ajuda externa
Aplique uma régua simples: novo código só entra testado. Código legado alterado ganha caracterização antes de refatorar. Assim, a rede de segurança cresce de forma orgânica.
Revise periodicamente gargalos e testes frágeis. Flakes devem ser estabilizados ou removidos. Mantenha testes rápidos e úteis ao time.
Se a equipe estiver sem banda ou experiência, considere apoio externo focado em testes de legado. A terceirização pode acelerar ganhos sem parar o roadmap. Veja terceirização de desenvolvimento de software.
Para projetos com grande superfície e integrações diversas, vale orquestrar o esforço dentro de um plano maior de desenvolvimento de software empresarial.
Perguntas frequentes
Preciso refatorar antes de criar testes no código legado?
Não necessariamente. Comece com testes de caracterização para congelar o comportamento atual. Depois, introduza pequenos seams e extrações seguras. Com a rede de testes crescendo, refatorações ficam mais seguras e rápidas.
Como evitar que os testes acessem produção por engano?
Separe ambientes com .env.testing, use credenciais exclusivas e bloqueios para e-mail e SMS. Aponte DB e endpoints para instâncias de teste. Execute os testes em containers ou VMs isoladas e use rollback transacional.
PHPUnit ou Pest funcionam bem com legado?
Ambos funcionam. O importante é conseguir isolar dependências e rodar rápido. Em código muito acoplado, foque primeiro em caracterização e seams; a escolha do runner é secundária. Em frameworks como Laravel, os dois se integram bem.
O que testar primeiro em um sistema PHP legado?
Comece por funções puras com alto valor de negócio e poucas dependências. Em seguida, cubra pontos críticos de integração, como repositórios e gateways. Transforme cada bug encontrado em teste para criar cobertura de regressão.
Quanto tempo leva para ver benefícios dos testes?
Depende do escopo e do estado do legado. Normalmente, após os primeiros módulos e bugs cobertos, o time já percebe menos regressões e mais confiança ao alterar código. A disciplina de manter o ritmo é o que consolida os ganhos.
Pronto para testar seu PHP legado com segurança?
Se você quer implementar ou acelerar a cobertura de testes em um sistema PHP legado, vamos conversar sobre o melhor caminho para o seu cenário.
Falar sobre testes no legado PHPVeja também
- Guia: checklist de pré‑deploy e plano de rollback rápido para aplicações PHP em produção
- Checklist de segurança para pipelines CI/CD com runners self‑hosted e secrets management
- Guia: centralização de logs com Fluentd e Elasticsearch para aplicações PHP em VPS
- Guia: organizar migrations e rollbacks em PostgreSQL para equipes pequenas