Yuri Developer
Dominando a Arquitetura de BDD
Guia técnico para estruturar cenários de comportamento, organizar pastas e manter uma documentação viva sem gerar ruído
Visão geral da arquitetura de BDD
Behavior-Driven Development (BDD) coloca o comportamento desejado em primeiro plano, promovendo a comunicação entre negócio, produto e engenharia por meio de uma linguagem comum. No cerne dessa arquitetura estão os Feature Files, que descrevem o comportamento em linguagem natural com rigor de critérios de aceitação, e as Step Definitions que traduzem esse comportamento para código executável. A arquitetura resultante busca manter a documentação legível ao redor do código, evitando divergências entre o que é descrito e o que é entregue.
Para obter ganhos reais, cada artefato deve ter responsabilidade bem definida: arquivos de features capturam o que testar, definições de passos conectam esse que testar ao dominio, e o domínio (camada de serviço/entidade) encapsula as regras de negócio. O objetivo é construir uma cadeia de referência clara para quem lê, sem dispersar a lógica de negócio em camadas superficiais.
- Feature Files devem expressar cenários com linguagem de domínio, estáveis e compreensíveis para leitores não técnicos.
- Step Definitions devem mapear passos a código mínimo e reutilizável.
- A camada de domínio deve encapsular as regras de negócio e facilitar reuso em cenários variados.
- Rerun/execução de cenários ocorre a partir de um runner que lê as features e orquestra a execução.
Estrutura de projeto e fluxo de execução
Uma arquitetura bem organizada para BDD evita acoplamentos desnecessários entre teste, lógica de negócio e interface. Recomenda-se uma separação clara de pastas que facilita a evolução do projeto:
- features/ – contém os arquivos de descrição de comportamento (features) organizados por domínio.
- features/step_definitions/ – mapeia passos para código, mantendo-os reutilizáveis.
- features/support/ – utilitários, hooks (before/after), data builders e configuração do runner.
- src/ domain/ service/ – camada de domínio com regras de negócio acessadas pelos passos.
- test-data/ – fontes previsíveis de dados para cenários (builders, factories).
Em termos de fluxo, o runner lê cada feature, aplica os cenários com dados de teste, executa os passos na ordem Given -> When -> Then e reporta o estado de cada cenário. O design deve buscar isolamento entre passos, para que alterações em um passo não acionem falhas desnecessárias em cenários não-relacionados.
Design de features e cenários
Escrever features eficazes requer cuidado com a linguagem, a granularidade e a reutilização. Abaixo estão diretrizes práticas:
- Use linguagem de domínio: termos que façam sentido para negócio, produto e desenvolvimento.
- Cenários curtos e específicos: cada cenário deve testar um único objetivo de comportamento.
- Background opcional: se houver pré-requisitos comuns a vários cenários, considere centralizá-los no Background.
- Dados de teste: prefira data tables para variação de entradas sem duplicar cenários.
- Scenario Outline para variação de dados: facilita cobrir várias situações com um único esqueleto de cenário.
- Critérios de aceitação explícitos: cada Then deve confirmar um resultado observável e verificável.
Exemplo conciso de boa prática abaixo pode servir como referência para escrita de features:
Exemplo de feature e mapeamento de passos
# features/checkout_basic.feature
Feature: Checkout básico
In order to concluir a compra
As a client
I want to finalizar o pedido com um pagamento válido
Scenario: Pagamento aprovado
Given o carrinho contém itens
When eu submeter o pagamento com cartão válido
Then o pedido deve ser criado com status "Confirmado"
# features/step_definitions/checkout_steps.js
// observação: este arquivo representa mapeamento de passos para código de domínio
const { Given, When, Then } = require('@cucumber/cucumber');
const { validarPedido } = require('../../src/domain/pedido');
Given('o carrinho contém itens', async function() {
// preparar estado do carrinho no domínio ou no serviço de teste
});
When('eu submeter o pagamento com cartão válido', async function() {
// acionar fluxo de pagamento e persistência de pedido
});
Then('o pedido deve ser criado com status {string}', async function(status) {
// validação do estado do pedido no storage/domain
const atual = await validarPedido(); // exemplo ilustrativo
if (atual.status !== status) {
throw new Error(`Status esperado ${status}, porém obtido ${atual.status}`);
}
});
Boas práticas e padrões
Para manter a base estável e legível ao longo do tempo, adote práticas que promovam acoplamento fraco, clareza e evolução controlada:
- Organize por domínio: agrupe features por área de negócio para facilitar leitura e evolução.
- Seja conservador com mudanças: cada alteração em um passo deve ter impacto claro apenas em cenários relacionados.
- Reutilize passos com parcimônia: passos muito genéricos tendem a esconder falhas; passos devem ter responsabilidade única.
- Hooks para configuração: use before/after para preparar ambientes de teste sem fumaças de falha repetidas.
- Etiquetas (tags) para classificação: @escopo, @regressao, @smoke ajudam a selecionar conjuntos de cenários para diferentes ciclos de execução.
- Documentação viva: mantenha as descrições de features atualizadas para refletir o comportamento real do sistema.
Gostou? Explore mais conteúdos
Este é apenas o começo. Em nosso blog, você encontra artigos aprofundados sobre arquitetura de software, padrões de projeto e boas práticas para estruturar comportamentos de forma sólida. Leia outros posts para ampliar seu entendimento e manter-se atualizado.
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!