Como estruturar testes por comportamento com BDD e Cucumber
1. Fundamentos do BDD e o papel do Cucumber
Behavior-Driven Development (BDD) é uma abordagem de desenvolvimento ágil que incentiva a colaboração entre desenvolvedores, QA e stakeholders de negócio. Seus princípios fundamentais incluem:
- Linguagem ubíqua: todos os envolvidos usam o mesmo vocabulário para descrever comportamentos
- Foco no comportamento: os cenários descrevem o que o sistema deve fazer, não como
- Colaboração contínua: os cenários são escritos antes do código, como especificações executáveis
A principal diferença entre BDD e TDD está no escopo: enquanto TDD foca em unidades de código (funções, métodos), BDD descreve comportamentos de alto nível que atravessam múltiplas camadas. Use BDD quando precisar alinhar equipes multidisciplinares e validar requisitos de negócio.
O Cucumber é o framework mais popular para BDD. Sua estrutura básica inclui:
- Arquivos .feature: cenários escritos em Gherkin
- Step definitions: código que implementa os passos Gherkin
- Hooks: funções de setup/teardown (Before, After)
2. Escrevendo cenários com Gherkin de forma eficaz
A sintaxe básica do Gherkin utiliza palavras-chave como Feature, Scenario, Given, When, Then, Background e Scenario Outline.
Exemplo de cenário bem escrito:
Feature: Login de usuário
Como um usuário registrado
Quero fazer login no sistema
Para acessar minhas funcionalidades
Background:
Dado que existem usuários cadastrados:
| email | senha |
| joao@email.com | abc123 |
| maria@email.com | xyz789 |
Scenario: Login bem-sucedido
Dado que estou na página de login
Quando preencho o campo "email" com "joao@email.com"
E preencho o campo "senha" com "abc123"
E clico no botão "Entrar"
Então devo ver a mensagem "Bem-vindo, João"
Boas práticas para cenários legíveis:
- Evite detalhes de implementação (não mencione IDs de elementos, classes CSS)
- Use exemplos concretos com dados reais
- Mantenha cenários atômicos (um comportamento por cenário)
Uso de Scenario Outline para múltiplas variações:
Scenario Outline: Login com diferentes credenciais
Dado que estou na página de login
Quando preencho o campo "email" com "<email>"
E preencho o campo "senha" com "<senha>"
E clico no botão "Entrar"
Então devo ver a mensagem "<mensagem>"
Examples:
| email | senha | mensagem |
| joao@email.com | abc123 | Bem-vindo, João |
| maria@email.com | xyz789 | Bem-vinda, Maria |
| invalido@email.com | 123 | Credenciais inválidas |
3. Organização de features e cenários em projetos reais
A estrutura de diretórios recomendada organiza features por domínio ou funcionalidade:
projeto/
├── features/
│ ├── login/
│ │ ├── login.feature
│ │ └── step_definitions/
│ │ └── login_steps.py
│ ├── pagamento/
│ │ ├── pagamento.feature
│ │ └── step_definitions/
│ │ └── pagamento_steps.py
│ └── support/
│ ├── hooks.py
│ └── environment.py
├── src/
└── config/
Uso de tags para organizar cenários:
@smoke @login
Scenario: Login com credenciais válidas
...
@regression @pagamento
Scenario: Processar pagamento com cartão de crédito
...
Para executar subconjuntos:
# Executar apenas cenários @smoke
cucumber --tags @smoke
# Executar cenários @regression, exceto @wip
cucumber --tags @regression and not @wip
Gerenciamento de dependências: evite acoplamento entre cenários usando hooks para setup isolado:
# hooks.py
Before:
- Limpar banco de dados de teste
- Criar dados básicos necessários
After:
- Capturar screenshot se falhou
- Limpar dados do cenário
4. Implementação de step definitions com Cucumber
Mapeamento de passos Gherkin para código usando Cucumber Expressions:
# login_steps.py
from behave import given, when, then
@given('que estou na página de login')
def step_impl(context):
context.browser.get('http://localhost:3000/login')
@when('preencho o campo "{campo}" com "{valor}"')
def step_impl(context, campo, valor):
element = context.browser.find_element_by_name(campo)
element.send_keys(valor)
@then('devo ver a mensagem "{mensagem}"')
def step_impl(context, mensagem):
assert mensagem in context.browser.page_source
Compartilhamento de estado entre steps usando o objeto context:
# environment.py
def before_scenario(context, scenario):
context.usuario_logado = None
context.dados_teste = {}
@given('que estou logado como "{tipo_usuario}"')
def step_impl(context, tipo_usuario):
context.usuario_logado = criar_usuario(tipo_usuario)
realizar_login(context, context.usuario_logado)
Uso de hooks avançados:
# hooks.py
from behave import fixture, use_fixture
@fixture
def setup_browser(context):
context.browser = webdriver.Chrome()
yield context.browser
context.browser.quit()
def before_all(context):
use_fixture(setup_browser, context)
def after_step(context, step):
if step.status == "failed":
context.browser.save_screenshot(f"screenshots/{step.name}.png")
5. Integração com frameworks de automação e asserções
Conectando Cucumber com Selenium para testes web:
# web_steps.py
@given('que acesso o site "{url}"')
def step_impl(context, url):
context.browser.get(url)
@when('clico no botão "{texto}"')
def step_impl(context, texto):
button = context.browser.find_element_by_xpath(f"//button[text()='{texto}']")
button.click()
Para testes de API com RestAssured (Java):
# api_steps.java
@Quando("envio uma requisição POST para {string} com o corpo:")
public void enviarPost(String endpoint, String body) {
response = RestAssured.given()
.contentType(ContentType.JSON)
.body(body)
.post(endpoint);
}
@Então("o status code deve ser {int}")
public void validarStatusCode(int statusCode) {
assertEquals(statusCode, response.getStatusCode());
}
Asserções comportamentais com AssertJ:
@Então("a resposta deve conter o campo {string} com valor {string}")
public void validarCampo(String campo, String valor) {
assertThat(response.jsonPath().getString(campo))
.isEqualTo(valor);
}
6. Estratégias de manutenção e reuso de cenários
Criação de steps genéricos reutilizáveis:
@given('que estou logado como "{tipo}"')
def step_impl(context, tipo):
credenciais = {
"admin": {"email": "admin@email.com", "senha": "admin123"},
"usuario": {"email": "user@email.com", "senha": "user123"},
"convidado": {"email": "guest@email.com", "senha": "guest123"}
}
dados = credenciais[tipo]
realizar_login(context, dados["email"], dados["senha"])
Versionamento de features: mantenha um arquivo de mapeamento entre histórias de usuário e cenários:
# user_stories_mapping.txt
US-001: Login de usuário -> features/login/login.feature
US-002: Cadastro de usuário -> features/cadastro/cadastro.feature
US-003: Processar pagamento -> features/pagamento/pagamento.feature
Evite fragilidade focando em comportamento, não em implementação:
# Ruim: dependente de implementação
@when('clico no elemento com CSS "#btn-login-123"')
# Bom: focado no comportamento
@when('clico no botão "Entrar"')
7. Execução e relatórios para equipes não-técnicas
Configuração de runner com JUnit e Cucumber Reports:
# TestRunner.java
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = "stepdefinitions",
tags = "@regression",
plugin = {
"pretty",
"html:target/cucumber-reports",
"json:target/cucumber.json",
"junit:target/cucumber.xml"
}
)
public class TestRunner {}
Geração de relatórios legíveis com Allure:
# Adicionar dependência no pom.xml
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-cucumber5-jvm</artifactId>
<version>2.24.0</version>
</dependency>
Integração contínua com CI/CD:
# .github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Cucumber tests
run: mvn test -Dcucumber.filter.tags="@smoke"
- name: Generate report
run: mvn allure:report
8. Armadilhas comuns e como evitá-las
Cenários muito longos: prefira cenários atômicos com no máximo 5-7 passos.
# Ruim: cenário monolítico
Scenario: Fluxo completo do usuário
Given que acesso o site
When faço login
And adiciono produto ao carrinho
And finalizo compra
And acesso meus pedidos
Then vejo o pedido confirmado
# Bom: cenários atômicos
Scenario: Login bem-sucedido
Given que acesso o site
When faço login com credenciais válidas
Then vejo a página inicial
Scenario: Adicionar produto ao carrinho
Given que estou logado
When adiciono o produto "Camiseta" ao carrinho
Then o carrinho deve conter 1 item
Dependência de ordem de execução: use dados isolados e evite estado global.
# Ruim: dependente de cenário anterior
@when('utilizo o ID do pedido criado anteriormente')
# Bom: dados isolados por cenário
@given('que crio um novo pedido')
def step_impl(context):
context.pedido_id = criar_pedido_unico()
Excesso de abstração: equilibre reuso e legibilidade.
# Ruim: genérico demais
@when('executo ação "{acao}" no elemento "{elemento}"')
# Bom: específico e legível
@when('clico no botão "Enviar"')
@when('preencho o campo "Email" com "{email}"')
Referências
- Documentação oficial do Cucumber — Guia completo sobre BDD, Gherkin e implementação com Cucumber em diversas linguagens
- Behave: BDD para Python — Tutorial prático de BDD com Python usando o framework Behave
- Cucumber com Selenium: Guia prático — Documentação oficial do Selenium sobre integração com BDD
- Allure Reports para Cucumber — Configuração de relatórios visuais para stakeholders com Allure
- BDD com Cucumber e Java: Curso completo — Tutorial da Baeldung sobre BDD com Cucumber, Spring Boot e JUnit
- Gherkin Reference — Referência completa da sintaxe Gherkin com exemplos práticos
- Cucumber Hooks e configurações — Documentação oficial sobre hooks, tags e configurações avançadas