BDD com Cucumber
Objective
Behavior-driven development (BDD), criado por Dan North, estende o TDD escrevendo a especificação em linguagem legível pelo negócio em vez de código de teste. Cucumber é a ferramenta de BDD mais comum na JVM: os cenários são escritos em Gherkin (passos Given/When/Then em inglês simples) dentro de arquivos .feature que as partes interessadas conseguem ler, e cada passo é ligado a um método Java de step definition (@Given/@When/@Then). O Cucumber executa a feature, roda os passos correspondentes e reporta o sucesso ou falha de cada cenário, transformando critérios de aceitação em especificações vivas e executáveis.
Use Cases
- Capturar critérios de aceitação como cenários que partes interessadas não técnicas (product owners, testers) conseguem ler e até ajudar a escrever.
- Transformar a estrutura
Given/When/Thenjá usada para rotular testes unitários em especificações executáveis de primeira classe. - Manter os requisitos permanentemente sincronizados com o código, já que um cenário desatualizado quebra o build.
- Conduzir o desenvolvimento de uma feature de cima para baixo a partir do comportamento de negócio, complementando os testes unitários TDD de baixo para cima.
- Fornecer uma linguagem compartilhada e onipresente entre negócio e engenharia, de modo que os testes de aceitação se tornem um instrumento de comunicação.
Deep Dive
O arquivo de feature em Gherkin
Os cenários vivem em um arquivo .feature em src/test/resources. Cada passo começa com uma palavra-chave; o texto é inglês simples:
plaintextFeature: Passengers Policy The company follows a policy of adding and removing passengers, depending on the passenger type and the flight type Scenario: Economy flight, regular passenger Given there is an economy flight When we have a regular passenger Then you can add and remove him from an economy flight And you cannot add a regular passenger to an economy flight more than once
Ligando os passos ao código Java
Cada passo em Gherkin é casado com um método de step definition anotado com @Given/@When/@Then. O método contém as asserções JUnit de fato:
javapublic class PassengerPolicy {
private Flight economyFlight;
private Passenger regularPassenger;
@Given("there is an economy flight")
public void thereIsAnEconomyFlight() {
economyFlight = new EconomyFlight("1");
}
@When("we have a regular passenger")
public void weHaveARegularPassenger() {
regularPassenger = new Passenger("Mike", false);
}
@Then("you can add and remove him from an economy flight")
public void youCanAddAndRemoveHimFromAnEconomyFlight() {
assertTrue(economyFlight.addPassenger(regularPassenger));
assertTrue(economyFlight.removePassenger(regularPassenger));
}
}O Cucumber casa cada linha do Gherkin com o texto da anotação, injeta os parâmetros capturados e executa os passos em ordem para cada cenário.
Livro vs. hoje: este capítulo tem o ferramental mais desatualizado do livro
O capítulo de BDD (2020) usa uma versão há muito obsoleta do Cucumber, de modo que quase nada da configuração dele compila hoje. Três mudanças concretas:
1.
info.cukesparaio.cucumber. O livro depende deinfo.cukes:cucumber-java:1.2.5. Esse groupId foi abandonado há anos; o Cucumber moderno (7.x) éio.cucumber, e os imports das step definitions mudaram decucumber.api.java.en.*paraio.cucumber.java.en.*:
plaintext<!-- book (dead) --> <!-- today --> <groupId>info.cukes</groupId> <groupId>io.cucumber</groupId> <artifactId>cucumber-java</artifactId> <!-- + cucumber-junit-platform-engine -->
2.
@RunWith(Cucumber.class)para a JUnit 5 Platform. O livro afirma literalmente que "não existe extensão Cucumber para JUnit 5 no momento em que este texto foi escrito" e usa o runner do JUnit 4. Isso deixou de ser verdade:cucumber-junit-platform-enginemaisjunit-platform-suiteexecutam features nativamente no JUnit 5:
java// book (JUnit 4) // today (JUnit 5 Platform Suite)
@RunWith(Cucumber.class) @Suite
@CucumberOptions(features="classpath:features") @IncludeEngines("cucumber")
public class CucumberTest { } @SelectClasspathResource("features")
public class RunCucumberTest { }3. Regex ancorada para Cucumber Expressions. O livro escreve os padrões de passo como regex ancorada (
@Given("^there is an economy flight$")). O Cucumber moderno usa Cucumber Expressions por padrão, então a string simples (@Given("there is an economy flight")) já é suficiente, com placeholders tipados como{int}/{string}quando é preciso capturar valores.
JBehave (Cap. 21.3) é uma alternativa de nicho. O livro também cobre o JBehave como uma segunda ferramenta de BDD; hoje o Cucumber domina o espaço de BDD na JVM, então vale saber que o JBehave existe, mas raramente é a escolha padrão para um projeto novo.
Trade-offs
- Uma camada de especificação legível custa uma indireção extra: cada cenário precisa de texto de feature e código de ligação, e uma incompatibilidade de digitação entre a linha do Gherkin e a string do
@Givengera um "undefined step" em vez de uma falha clara:
java@Given("there is an economy flight") // step text
// feature says "Given there is a economy flight" → undefined step, scenario skipped- O BDD só compensa quando não desenvolvedores realmente leem os cenários: se apenas os engenheiros escrevem e leem os arquivos de feature, o Gherkin é puro overhead sobre testes JUnit simples; o valor está na linguagem compartilhada com as partes interessadas, não na sintaxe.
- A reutilização de passos pode criar acoplamento oculto: compartilhar step definitions entre features mantém o código enxuto (DRY), mas significa que o estado/setup de um passo pode vazar entre cenários, dificultando a localização de falhas.
- Os arquivos de feature são mais um artefato a manter atualizado: por serem executáveis, não podem apodrecer silenciosamente, mas um conjunto grande de cenários é manutenção real, e especificar demais um comportamento trivial em Gherkin é mais lento de alterar do que um teste unitário.