Introdução
O ruído atual em torno do hype de IA na engenharia de software é ensurdecedor. Com novos modelos e ferramentas agentic surgindo diariamente, é fácil cair na armadilha de acreditar que as práticas tradicionais de engenharia disciplinada se tornaram obsoletas. No entanto, acreditamos que, agora mais do que nunca, precisamos voltar às nossas origens. Neste período “Cambriano” de rápida evolução, a indústria está deixando de lado o conhecimento por trás das práticas fundamentais da engenharia de software — aquelas que, no passado, evitaram que o desenvolvimento rápido e não verificado mergulhasse no caos.
Nossa jornada pessoal para nos tornarmos engenheiros melhores começou quando mergulhamos nas obras fundamentais de Kent Beck e Martin Fowler. Seus livros sobre Extreme Programming (XP), Test-Driven Development (TDD) e Refactoring, juntamente com os princípios atemporais de The Pragmatic Programmer, mudaram nossa perspectiva de simplesmente “escrever código” para realmente construir software. Ao adotar essas disciplinas, aprendemos que qualidade não é algo adjacente — é o núcleo do trabalho. As pérolas escondidas do TDD, em particular, não são apenas técnicas legadas; são pilares essenciais para construir o futuro da tecnologia de forma eficaz. Esses recursos nos ensinaram que ser um grande engenheiro não tem a ver com complexidade e velocidade, mas sim com confiabilidade e simplicidade.
A disciplina da prova
Em sua essência, o TDD é um processo disciplinado no qual você escreve um teste antes do código propriamente dito. Isso garante que os requisitos estejam claros e que o software seja testável desde o primeiro dia. O processo segue o ciclo red-green-refactor, que envolve:
- Escrever um pequeno teste que falha, definindo o comportamento desejado (Red)
- Escrever o mínimo de código necessário para que o teste passe (Green)
- Limpar e melhorar o design do código, garantindo que o teste continue passando (Refactor)
No entanto, nossa compreensão desse processo evoluiu com a experiência. No início de nossas carreiras, víamos a escrita de testes apenas como uma prática de verificação, uma forma de garantir que o código fizesse o que deveria. À medida que amadurecemos, começamos a nos concentrar em métricas como cobertura de testes ou técnicas avançadas como mutation testing.
Mas a percepção final é mais profunda. Os testes são, na verdade, uma forma de documentação viva. Eles comprovam que o código é válido ao criar estruturas que demonstram a hipótese que o código sustenta. Edsger Wybe Dijkstra descobriu esse princípio em 1960, ao perceber que programadores poderiam usar uma hierarquia de postulados semelhante à dos matemáticos. Começar a implementação com testes oferece tudo isso naturalmente — mas ainda há algo mais valioso a ser descoberto.
Apoie-se no feedback
"Um teste é o primeiro usuário do seu código" é uma das principais lições do livro The Pragmatic Programmer, de Andy Hunt e Dave Thomas. É simples, direto e claro: quando escrevemos um teste antes da implementação, ele se torna a primeira entidade a verificar a funcionalidade a ser construída.
Essa mudança de perspectiva é extremamente valiosa. Se um teste é difícil de escrever, é um sinal claro de que o código será difícil de usar. Seja ao projetar uma API REST ou ao definir métodos públicos em uma classe, estamos criando uma arquitetura que molda como outros desenvolvedores irão interagir com nosso código. Ao tratar o teste como o primeiro cliente do código, naturalmente projetamos interfaces mais intuitivas e garantimos que o software nasça com usabilidade em mente.
O poder desse mecanismo vai além da engenharia de software. Ele está profundamente enraizado na psicologia comportamental, especialmente nos trabalhos de Daniel Kahneman e Richard H. Thaler. Como seres humanos, dependemos de feedback para melhorar nosso desempenho — inclusive ao programar.
A melhor maneira de ajudar as pessoas a melhorar seu desempenho é fornecer feedback. Sistemas bem projetados mostram quando as pessoas estão indo bem e quando estão cometendo erros.
Evitando overengineering
Um dos benefícios mais imediatos do TDD é que ele força você a implementar a quantidade mínima de código necessária para que um teste passe, já que é fácil se empolgar e escrever código “por precaução”. Esse é um ponto destacado por Kent Beck em Extreme Programming Explained. Sem um teste falhando para satisfazer, desenvolvedores frequentemente caem na armadilha do overengineering.
Ao seguir o ciclo do TDD, a base de código resultante se torna significativamente mais enxuta. Você evita a desordem de métodos não utilizados e lógica especulativa. O resultado é um código mais limpo, que faz exatamente o que é necessário — nada mais, nada menos — reduzindo a superfície para possíveis bugs.
Reduzir a superfície para possíveis bugs não é apenas um luxo técnico, mas uma estratégia crítica de negócio. Quanto mais tarde um defeito é identificado, exponencialmente mais caro se torna corrigi-lo. Isso acontece porque o trabalho construído sobre componentes defeituosos inevitavelmente precisará ser descartado ou retrabalhado. Corrigir um bug após o lançamento pode ser até 150 vezes mais caro do que resolvê-lo durante o design inicial. Assim, evitar overengineering não é apenas uma preferência de codificação — é uma necessidade financeira.
Refatoração: projetando com propósito
A etapa final do ciclo do TDD — Refactoring — é onde a verdadeira mágica acontece. Como você já tem um teste passando como rede de segurança, pode reorganizar seu código sem medo, aderindo aos princípios fundamentais de engenharia de software. Este é o momento de aplicar os princípios SOLID, remover duplicações (DRY) e garantir que você não esteja construindo coisas que ainda não precisa (YAGNI). E com menos tokens sendo consumidos.
Refatorar com TDD não é apenas limpar o código; trata-se de design intencional. Isso permite utilizar padrões como Strategy ou Factory apenas quando o código realmente exige. O processo de desenvolvimento deixa de ser uma corrida caótica e se transforma em um refinamento disciplinado da arquitetura.
Design evolutivo
O TDD possibilita o design evolutivo, permitindo adiar detalhes específicos de implementação até o último momento responsável. Em vez de depender de um grande design antecipado (BUFD), que frequentemente resulta em sistemas rígidos e superprojetados, difíceis de mudar quando a realidade do negócio inevitavelmente evolui, o TDD permite que o design emerja de forma orgânica, guiado por requisitos reais em vez de suposições.
Essa abordagem mantém o sistema flexível. Ao tomar decisões quando temos o máximo de informação possível, evitamos a falácia do custo afundado em uma arquitetura que não se adapta às necessidades em evolução do projeto.
No entanto, a evolução não se aplica apenas a novas funcionalidades — ela também é essencial para sistemas existentes. Quando precisamos evoluir código legado (código sem testes) para atender novas necessidades de negócio, criar uma rede de segurança antes de qualquer mudança é inegociável. É a única maneira de distinguir o que está funcionando do que não está. Na prática, temos duas opções: Editar e rezar ou Cobrir e modificar (trabalhar com segurança).
Não acho que nenhum de nós escolheria um cirurgião que operasse com uma faca de manteiga só porque ele trabalha com cuidado.
Desenvolvimento orientado por hipóteses
Na arquitetura de software, frequentemente enfrentamos momentos em que o caminho correto não é imediatamente óbvio. Em vez de debater essas decisões em reuniões longas, podemos usar o TDD como um laboratório para desenvolvimento orientado por hipóteses. Nesse contexto, o teste não serve apenas para verificar um bug ou testar uma nova funcionalidade; ele é um experimento projetado para validar se uma determinada abordagem estrutural é viável. Tratamos nossas suposições arquiteturais como hipóteses que precisam ser comprovadas por meio da implementação.
Por exemplo, imagine que a equipe esteja debatendo se uma lógica complexa de cálculo deve ser uma biblioteca interna compartilhada ou um microserviço independente. Ao escrever uma suíte de testes que simula a chamada do serviço e implementar o mínimo de código para que esses testes passem, você pode descobrir que o contrato de dados é tão grande e complexo que o serviço simples se torna um pesadelo de manutenção. O teste falha no experimento de viabilidade antes que semanas sejam gastas em infraestrutura. O processo de TDD também permite tomar decisões no Último Momento Responsável.
Reduzindo a complexidade acidental
Em seu ensaio clássico No Silver Bullet, Fred Brooks faz uma distinção entre complexidade essencial — a dificuldade inerente ao problema que estamos resolvendo — e complexidade acidental, que é a dificuldade que criamos por meio de design ruim, excesso de funcionalidades e ferramentas inadequadas. Brooks argumentou que nenhuma tecnologia ou técnica de gestão isolada poderia proporcionar uma melhoria de produtividade de dez vezes. Em vez disso, ele defendia o crescimento orgânico do software por meio de desenvolvimento incremental, em vez de construí-lo todo de uma vez.
O TDD é talvez a ferramenta mais eficaz para cultivar esse crescimento orgânico. Ao nos forçar a escrever apenas o código necessário para passar em um teste específico, o TDD atua como um filtro que elimina a complexidade acidental antes que ela entre na base de código. Ele impede a criação de abstrações desnecessárias que, segundo Brooks, levariam ao “pântano” da engenharia de software. Quando focamos no menor incremento possível, garantimos que o software permaneça tão simples quanto o problema permitir.
Agentes de IA funcionam melhor com TDD
À medida que entramos na era da programação assistida por IA, a disciplina do TDD nunca foi tão relevante. Ferramentas como Claude Code e frameworks agentic avançados funcionam melhor quando possuem critérios claros de conclusão. Sem uma abordagem test-first, agentes de IA frequentemente caem na armadilha do AI Slop e AI Smells — gerando grandes blocos de código que parecem corretos, mas sofrem com alucinações ou complexidade desnecessária oculta.
Ao instruir um agente de IA a escrever o teste primeiro — ou ao fornecer o teste nós mesmos — criamos as barreiras necessárias para manter o agente focado. Isso força a IA a fornecer a implementação mínima e funcional necessária, prevenindo efetivamente a complexidade acidental que Brooks já alertava. O TDD transforma a IA de uma ferramenta imprevisível em um parceiro de engenharia disciplinado, que itera até alcançar um resultado determinístico e verificado.
Conclusão: ATDD e o futuro da orquestração
O software surgiu como produto comercial há aproximadamente 75 anos e evoluiu para uma disciplina de engenharia cerca de 55 anos atrás, impulsionado pela crise do software e pela conferência da NATO de 1968. Desde então, métodos e metodologias comprovados foram desenvolvidos para lidar com os desafios ao longo do caminho. O Extreme Programming (XP) reúne muitas dessas soluções, com o TDD se destacando como um de seus pilares.
O TDD existe no equilíbrio entre disciplina e flexibilidade, controle e clareza. Seja usando testes como laboratório experimental para hipóteses arquiteturais ou como filtro para complexidade acidental, o objetivo é fazer o software crescer de forma orgânica e intencional.
Olhando para o futuro, em um mundo cada vez mais dominado pela IA — onde agentes de código escrevem milhares de linhas de forma não determinística — começamos a enxergar problemas que podem surgir nesse cenário. São desafios que já enfrentamos no passado e para os quais provavelmente já conhecemos as soluções.
O Acceptance test-driven development (ATDD) surge como a evolução natural dessa mentalidade, especialmente à medida que começamos a orquestrar múltiplos agentes de IA. Enquanto os testes unitários tratam do como, o ATDD foca no o quê e incorpora o porquê a partir da perspectiva de negócio. Ao definir critérios de aceitação de alto nível antes que qualquer agente inicie seu trabalho, podemos orquestrar fluxos complexos com múltiplos agentes com confiança.
O ATDD provavelmente se tornará a principal interface para engenheiros de software, por meio de testes de aceitação — e nossas equipes agentic trabalharão para tornar esses testes verdes usando TDD.
Aviso: As afirmações e opiniões expressas neste artigo são de responsabilidade de quem o assina, e não necessariamente refletem as posições da Thoughtworks.