O grande mal-entendido sobre TDD (2022)
Test-driven development (TDD) e unit testing são examinados aqui, com muitos argumentando que testes fortemente acoplados aos detalhes de implementação criam suítes frágeis que dificultam refatorações e desperdiçam esforço. Os comentaristas contrapõem testes unitários de baixo nível a testes de integração e end-to-end de nível mais alto, debatendo onde está o melhor custo-benefício, quanta cobertura é realmente útil e se os testes devem, acima de tudo, validar o comportamento a partir de uma fronteira de usuário ou de sistema. Por trás disso, há uma preocupação mais ampla com metodologias dogmáticas: alguns veem o TDD como um poderoso auxílio ao design quando aplicado com cuidado, enquanto outros o tratam como uma ferramenta situacional que deve ser escolhida com base no contexto, nos objetivos de qualidade e na complexidade do sistema.
Significado de “unit” em unit testing
- A afirmação do artigo de que “unit” originalmente se referia a um teste isolado é fortemente contestada.
- Vários comentaristas citam definições anteriores (por exemplo, “small compilable module, ~100 LOC”) em que a unidade é claramente o código sob teste, não o teste.
- O consenso no tópico: a linguagem derivou, mas a maioria dos praticantes e documentos históricos trata a unidade como um componente de código (função, classe, módulo), embora alguns aceitem “independent test” como uma noção moderna útil.
TDD é sobre testing ou design?
- Um grupo: o grande mal-entendido é tratar TDD como testing; ele é principalmente uma prática de design que usa testes para moldar APIs e a separação de responsabilidades.
- Grupo oposto: TDD-como-design é superestimado ou prejudicial; habilidade e julgamento de design importam mais, e o TDD oferece orientação de design fraca, especialmente para devs menos experientes.
- Alguns dividem o termo: Test-Driven Development vs Test-Driven Design, argumentando que eles são frequentemente confundidos.
Unit vs integration/E2E tests
- Muitos argumentam que testes funcionais/de integração de alto nível oferecem melhor valor:
- Mais próximos do comportamento visível ao usuário.
- Mais estáveis durante refatorações.
- Evitam o acoplamento excessivo dos testes à implementação e a proliferação de mocks.
- Outros defendem a pirâmide de testes tradicional: numerosos testes unitários rápidos e isolados capturam casos extremos e bugs que testes mais amplos podem deixar passar, especialmente para algoritmos complexos ou bibliotecas.
- Compromisso comum:
- Testes unitários para lógica pura e complexa ou bases reutilizáveis.
- Testes de integração/E2E para fluxos de negócio e fronteiras HTTP/JSON ou de UI.
Testes frágeis e dor na refatoração
- Reclamação frequente: refatorar “sem mudar o comportamento” quebra muitos testes de baixo nível.
- Diagnósticos: os testes são excessivamente white-box, fazem over-mocking ou afirmam detalhes internos irrelevantes.
- Estratégias mencionadas:
- Focar os testes no comportamento observável externamente.
- Aceitar que alguns testes iniciais, focados na implementação, devem ser removidos ou evoluir quando testes de comportamento de nível mais alto existirem.
- Usar testes mais como um suporte de segurança para refatoração do que como restrições de design.
Dogmatismo, fluxo de trabalho e métricas
- Debate sobre “nunca mudar código sem um teste vermelho”: alguns veem isso como central para TDD, outros como ritual desnecessário, especialmente durante trabalho exploratório.
- Ceticismo amplo sobre TDD de culto, cobertura obrigatória de 100% e coverage como KPI (lei de Goodhart).
- Vários enfatizam contexto: risco do domínio, linguagem (dinâmica vs fortemente tipada), requisitos não funcionais e habilidade da equipe devem orientar a estratégia de testes, não uma única metodologia universal.