TDD vs BDD: Principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um
TDD ajuda os desenvolvedores a projetar o código correto. O BDD ajuda toda a equipe a concordar sobre o que o software deve fazer. Veja o mesmo recurso de desconto criado com cada abordagem e saiba como os dois funcionam juntos.
- TDD significa escrever primeiro um teste com falha, depois apenas código suficiente para passar e depois arrumar.
- BDD descreve o comportamento em linguagem compartilhada, geralmente como Dado, Quando, Então.
- O TDD orienta principalmente o design do código. O BDD constrói principalmente um entendimento compartilhado.
- Eles não são rivais e muitas equipes usam BDD para recursos e TDD para detalhes abaixo.
O debate TDD vs BDD muitas vezes é enquadrado como a escolha de um lado. Na realidade, eles respondem a perguntas diferentes. O desenvolvimento orientado a testes pergunta se o código faz o que o desenvolvedor pretendia, enquanto o desenvolvimento orientado a comportamento pergunta se a equipe concorda com o que o software deveria fazer.
Para tornar a diferença concreta, construímos um pequeno recurso de desconto em cada abordagem. Executamos o código no Python 3.13, usando a biblioteca padrão, e os resultados abaixo vêm dessa execução. A parte do BDD usa um pequeno corredor próprio para mostrar o mecanismo, então um projeto real usaria uma ferramenta como Cucumber ou se comportaria em vez disso.
A resposta curta
Desenvolvimento orientado a testes (TDD) é uma prática de codificação. Você escreve um pequeno teste com falha, faz com que ele passe com o código mais simples e depois melhora o design. Os testes são escritos em código e principalmente por desenvolvedores. Kent Beck popularizou a prática.
Desenvolvimento orientado ao comportamento (BDD) é uma prática de colaboração que surgiu do TDD, e Dan North introduziu o termo. Desenvolvedores, testadores e empresários concordam em exemplos de como o sistema deve se comportar, escritos em linguagem simples, e esses exemplos podem então ser automatizados.
TDD vs BDD lado a lado
| Aspecto | TDD | BDD |
|---|---|---|
| Foco principal | Código correto e bem projetado | Compreensão compartilhada do comportamento |
| Escrito em | Linguagem de programação | Linguagem simples, como pepino |
| Escrito por | Desenvolvedores | Negócios, controle de qualidade e desenvolvedores juntos |
| Legível por | Desenvolvedores | Todos da equipe |
| Nível típico | Unidade | Recurso ou aceitação |
| Ponto de partida | Um teste com falha | Uma conversa com exemplos |
| Ciclo | Vermelho, verde, refatorar | Descubra, formule, automatize |
| Ferramentas comuns | pytest, JUnit, brincadeira | Pepino, comporte-se, SpecFlow, Reqnroll |
O recurso
Aqui está a regra de negócios que implementaremos: Pedidos de 1.000 ou mais recebem 10% de desconto. Os membros ganham 5% de desconto extra nesses pedidos.
Isso pode parecer claro, mas já é ambíguo. Um membro que gasta 500 ganha 5% de desconto? A frase diz “essas ordens”, então presumimos que não. Tenha isso em mente, porque esse é exatamente o tipo de questão que o BDD foi projetado para trazer à tona antecipadamente.
Construindo com TDD
O TDD segue um pequeno loop conhecido como red, green, refactor:
| Passo | O que você faz |
|---|---|
| Vermelho | Escreva um teste para comportamento que ainda não existe e observe-o falhar |
| Verde | Escreva o código mais simples que faça passar |
| Refatorar | Melhore o código, mantendo os testes verdes |
Estes são nossos testes, com um para cada regra:
class DiscountTests(unittest.TestCase):
def test_no_discount_below_1000(self):
self.assertEqual(discount(999, False), 0)
def test_ten_percent_from_1000(self):
self.assertEqual(discount(1000, False), 100)
def test_member_gets_extra_five_percent(self):
self.assertEqual(discount(1000, True), 150)
Em seguida, escrevemos o código em três estágios, executando os testes após cada um. Uma sessão estrita de TDD adiciona um teste por vez e mostra a mesma progressão:
Etapa 1: a coisa mais simples
def discount(total, is_member):
return 0
# 3 tests, 2 failed
# test_member_gets_extra_five_percent
# test_ten_percent_from_1000
Retornar zero parece bobo, mas prova que os testes podem realmente falhar, e o único teste em que ele passa é a regra “sem desconto”.
Etapa 2: adicionar o limite
def discount(total, is_member):
if total >= 1000:
return total * 0.1
return 0
# 3 tests, 1 failed
# test_member_gets_extra_five_percent
Etapa 3: adicionar membros e limpar
def discount(total, is_member):
rate = 0.1 if total >= 1000 else 0
if is_member:
rate += 0.05
return round(total * rate, 2)
# 3 tests, 0 failed
Todos os três passam. Mas leia esta versão com atenção. Dá ao membro que gasta 500 um desconto de 5%, o que não é o que presumimos que a regra significava. Nenhum teste cobriu esse caso, então nada o sinalizou. O TDD protege apenas o comportamento que você pensou em testar. Essa é uma limitação real e a razão pela qual as conversas sobre exemplos são tão importantes.
Por que o ciclo é importante
- O vermelho prova que o teste funciona. Um teste que nunca falhou pode não estar testando nada.
- Verde mantém você focado. Você escreve apenas o código necessário, o que ajuda a evitar excesso de engenharia.
- Refactor mantém alta qualidade. Os testes dão a você a confiança necessária para alterar o design com segurança.
- Isso influencia o design. O código que é difícil de testar geralmente está muito acoplado e você percebe imediatamente.
Nosso tutorial pytest mostra uma maneira mais conveniente de escrever testes como esses, e a análise de valor limite explica por que escolhemos 999 e 1000 como valores.
Construindo com BDD
O BDD começa com uma conversa, não com ferramentas. Muitas vezes é uma reunião curta conhecida como os três amigos, com um responsável pelo produto, um desenvolvedor e um testador discutindo exemplos concretos:
Tester: What about a member spending 500?
Product: Good question. No discount. It has to reach 1000 first.
Dev: And exactly 1000 counts?
Product: Yes, 1000 or more.
Essa troca teria detectado o problema em nosso código TDD. Eles então registram seus exemplos em um formato estruturado chamado Gherkin:
Feature: Order discounts
Scenario: Member reaches the discount threshold
Given a member with a cart worth 1000
When they view the amount to pay
Then the amount to pay is 850
Scenario: Member below the threshold gets no discount
Given a member with a cart worth 500
When they view the amount to pay
Then the amount to pay is 500
| Palavra-chave | Objetivo |
|---|---|
| Dado | A situação inicial |
| Quando | A ação ou evento |
| Então | O resultado esperado |
Qualquer pessoa da equipe pode ler isso e dizer “sim, está certo” ou “não, está errado”. Mesmo que você nunca o automatize, o cenário já realizou um trabalho útil.
Conectando etapas ao código
As ferramentas conectam cada linha de linguagem simples a uma função, chamada definição de etapa. Para mostrar a ideia, nós mesmos escrevemos uma pequena versão e executamos o primeiro cenário:
@step("a member with a cart worth 1000")
def given(ctx):
ctx["cart"] = Cart(is_member=True)
ctx["cart"].add(1000)
@step("they view the amount to pay")
def when(ctx):
ctx["amount"] = ctx["cart"].payable()
@step("the amount to pay is 850")
def then(ctx):
assert ctx["amount"] == 850
# ok Given a member with a cart worth 1000
# ok When they view the amount to pay
# ok Then the amount to pay is 850
Ferramentas reais adicionam parâmetros, para que uma única etapa possa corresponder a “1000” e “500”, além de relatórios. O resultado é documentação viva: uma descrição simples do sistema que falha ruidosamente se deixar de ser verdade.
Dica: O valor do BDD está na conversa. As equipes que têm uma pessoa escrevendo Gherkin sozinha após a conclusão do código recebem o custo da ferramenta, com quase nenhum benefício.
Escrevendo bons cenários
| Fraco | Melhor |
|---|---|
Quando clico no botão com id "btn-77" |
Quando aplico o cupom |
Então o resultado está correto |
Então o valor a pagar é 850 |
| Vinte etapas em um cenário | Um comportamento por cenário |
| Descreve a tela | Descreve a intenção do negócio |
Os cenários que descrevem cliques e IDs de campo são interrompidos a cada reformulação e os leitores empresariais param de lê-los. Mantenha os detalhes da interface do usuário no código da etapa. Esses cenários são primos próximos dos exemplos em caso de teste versus cenário de teste, e também são frequentemente usados em testes de aceitação do usuário.
Usando os dois juntos
Os dois se encaixam naturalmente em um fluxo de trabalho “de fora para dentro”:
- A equipe concorda com o comportamento e escreve um cenário BDD. Ele falha, porque nada existe ainda.
- Um desenvolvedor usa TDD para construir as pequenas unidades necessárias.
- Os testes de unidade são aprovados e, eventualmente, o cenário também.
- Tudo é executado em seu pipeline de CI/CD.
Mantenha cada um no nível certo. Você deve ter alguns cenários para comportamentos importantes e muitos testes de unidade rápidos para detalhes. Nosso guia para testes de unidade versus integração cobre o equilíbrio.
Ferramentas
| Idioma | TDD (teste unitário) | BDD |
|---|---|---|
| Pitão | pytest, unittest | comporte-se, pytest-bdd |
| JavaScript | Brincadeira, Vitest | Cucumber.js |
| Java | JUnit, TestNG | Pepino-JVM |
| .NET | xUnit, NUnit | Reqnroll, SpecFlow |
Quando usar cada
TDD se adapta bem quando:
- Você está escrevendo lógica complexa, como preços, análise ou permissões.
- Você está corrigindo um bug, pois pode reproduzi-lo primeiro com um teste com falha.
- Você está trabalhando em um código que mudará frequentemente.
O BDD se adapta bem quando:
- As regras de negócios são complexas e os mal-entendidos custam caro.
- Pessoas não técnicas realmente lerão e discutirão os cenários.
- Várias equipes precisam de uma definição compartilhada de como algo se comporta.
Pode ser um exagero quando: você está escrevendo um protótipo descartável ou explorando uma ideia técnica pouco clara. Muitos desenvolvedores aumentam primeiro e depois reescrevem com testes. As ferramentas BDD também são um investimento ruim se ninguém fora da engenharia analisar os cenários. Nesse caso, testes simples com bons nomes são mais simples.
Erros comuns
- Tratar o BDD como uma ferramenta de teste. Cucumber sem colaboração é apenas uma maneira detalhada de escrever testes.
- Testando implementação em TDD. Testes vinculados a detalhes privados são interrompidos durante cada refatoração.
- Ignorando a etapa de refatoração. Você passa nos testes além de um código confuso.
- Escrever testes após o código e chamá-lo de TDD. Isso ainda é útil, mas você perde o feedback do design.
- Escrever muitos cenários de UI. Eles se tornam lentos e instáveis. Consulte testes instáveis.
- Perseguir números de cobertura. Uma boa cobertura ainda ignora comportamentos que ninguém considerou, como mostra nosso exemplo.
Considerações finais
O TDD ajuda você a construir a coisa certa e o BDD ajuda a construir a coisa certa. Nosso exemplo de desconto mostrou os dois lados: o código passou em todos os testes, mas uma simples conversa revelou uma suposição errada. Experimente em seu próximo pequeno recurso. Escreva três exemplos concretos com um colega antes de qualquer código e, em seguida, construa um teste com falha de cada vez.
Perguntas frequentes
Qual é a principal diferença entre TDD e BDD?
O TDD se concentra em desenvolvedores que escrevem testes de código com falha antes da implementação. O BDD se concentra em uma equipe inteira concordando com o comportamento usando exemplos em linguagem simples.
Você pode usar TDD e BDD juntos?
Sim, e muitas equipes o fazem. Os cenários BDD definem recursos externos e o TDD orienta as unidades menores de código abaixo deles.
O BDD é melhor que o TDD?
Nenhum dos dois é melhor no geral, pois resolvem problemas diferentes. O BDD ajuda no entendimento compartilhado e o TDD ajuda no design do código e no feedback rápido.
O que é pepino?
Gherkin é um formato estruturado de linguagem simples para escrever cenários, usando palavras-chave como Dado, Quando e Então. Ferramentas como o Cucumber podem executá-lo.
O TDD retarda o desenvolvimento?
Pode parecer mais lento no início, especialmente durante o aprendizado. As equipes geralmente recuperam tempo com menos bugs e mudanças mais seguras, embora o efeito varie de acordo com o projeto.
Continue aprendendo
Precisa de ajuda com testes, WordPress ou SEO?
Ajudamos as equipes a fornecer software melhor e a expandir seus sites. Conte-nos no que você está trabalhando e sugeriremos um próximo passo claro.
Mais guias sobre testes, IA e desenvolvimento moderno
Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.
Explicação das alucinações de IA: por que os modelos de linguagem inventam as coisas e como reduzi-las
Explicação das alucinações de IA: o que são, por que os modelos de linguagem as produzem, tipos comuns, como reduzi-las com aterramento e instruções…
Leia o artigo →DevOpsVariáveis de ambiente explicadas: arquivos .env, segredos e configuração bem feitos
Variáveis de ambiente explicadas com exemplos de Python que executamos: leitura e configuração, por que cada valor é uma string, arquivos .env, hera…
Leia o artigo →Desenvolvimento de softwareExplicação da limitação de taxa de API: algoritmos, cabeçalhos e tratamento de erros 429
Limitação de taxa de API explicada com uma simulação que executamos: token bucket, janela fixa, janela deslizante, 429 respostas, cabeçalhos de limi…
Leia o artigo →Automação de testesZombando em Python: unittest.mock, patch e os erros que fazem os testes mentirem
Zombaria em Python com unittest.mock, testado: Mock e return_value, side_effect, patch onde um nome é usado, especificação e autospec, zombaria de d…
Leia o artigo →Teste de softwareCobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa
Cobertura de código explicada com uma execução cobertura.py real: cobertura de linha, ramal e condição, leitura de um relatório, por que 100% de cob…
Leia o artigo →Bancos de dados e SQLDELETE vs TRUNCATE vs DROP em SQL: diferenças, reversão e quando usar cada um
DELETE vs TRUNCATE vs DROP explicado com SQL testado: o que cada um remove, rollback, aciona, redefinição de identidade, velocidade e como PostgreSQ…
Leia o artigo →Bancos de dados e SQLExibições SQL explicadas: crie, use e evite as armadilhas, com exemplos
Visualizações SQL explicadas com consultas que executamos: criação de uma visualização, resultados ao vivo, ocultação de colunas, desempenho, visual…
Leia o artigo →Desenvolvimento de softwareInterface TypeScript vs tipo: diferenças reais, testadas com o compilador
Interface TypeScript versus tipo, verificada com o compilador real: o que ambos podem fazer, o que apenas um pode fazer, mesclagem de declarações, e…
Leia o artigo →Desenvolvimento de softwarePython *args e **kwargs explicados: funções flexíveis com exemplos claros
Python *args e **kwargs explicados com o código que executamos: empacotamento e descompactação, ordem dos parâmetros, argumentos somente de palavras…
Leia o artigo →Desenvolvimento de softwareFunções Python Lambda: sintaxe, usos reais e quando não usá-los
Funções lambda do Python explicadas com o código que executamos: sintaxe, classificação com chave, mapa e filtro, fechamentos, armadilha de ligação …
Leia o artigo →Inteligência artificialO que são agentes de IA? Como eles funcionam, com um loop de agente simples que você pode executar
O que são agentes de IA? Um guia simples para o loop do agente, ferramentas, memória e proteções, com um pequeno loop Python que executamos, riscos …
Leia o artigo →DevOpsMonitoramento versus observabilidade: a verdadeira diferença, com logs, métricas e rastreamentos
Explicação do monitoramento versus observabilidade: falhas conhecidas e desconhecidas, logs, métricas e rastreamentos, um passo a passo do incidente…
Leia o artigo →DevOpsDocker Volumes vs Bind Mounts: persista os dados do contêiner da maneira certa
Volumes Docker versus montagens de ligação explicadas: por que os dados do contêiner desaparecem, volumes nomeados, montagens de ligação, tmpfs, exe…
Leia o artigo →Automação de testesTDD vs BDD: principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um
TDD vs BDD explicado com um recurso de desconto construído em ambos os sentidos: refatoração vermelho-verde com o código que executamos, cenários da…
Leia o artigo →Desenvolvimento de softwareGit merge vs rebase: diferenças, exemplos e quando usar cada um
Git merge vs rebase explicado com saída real do log, uma tabela comparativa, a regra de ouro do rebase e um fluxo de trabalho seguro para equipes.
Leia o artigo →Desenvolvimento de softwareList comprehension em Python: sintaxe, exemplos e quando evitar
List comprehension em Python explicada: sintaxe, filtros, if-else, laços aninhados, tempos medidos e quando um laço for comum é a melhor escolha.
Leia o artigo →Bancos de dados e SQLSQL JOIN explicado com exemplos (Inner, Left, Right, Full)
Entenda os joins SQL passo a passo com tabelas e consultas de exemplo: INNER, LEFT, RIGHT, FULL, CROSS e SELF, além de erros comuns e dicas de desem…
Leia o artigo →