Behavior-Driven Development (BDD) (como se discute en Cucumber BDD Automation: Complete Guide to Behavior-Driven Development Testing) cierra la brecha entre requisitos de negocio y automatización de pruebas, creando un entendimiento compartido entre product owners, desarrolladores y testers. Esta guía completa explora cómo implementar efectivamente BDD (como se discute en Gauge Framework Guide: Language-Independent BDD Alternative to Cucumber) desde los requisitos iniciales hasta la integración con CI/CD.
Entendiendo BDD: Más que Solo Testing
BDD es a menudo malinterpretado como meramente “pruebas escritas en inglés simple”. Es en realidad una práctica colaborativa que cambia cómo los equipos piensan sobre y entregan software.
Los Tres Amigos
BDD se centra en la conversación de los “Tres Amigos”:
- Product Owner/Business Analyst: Define qué necesita ser construido
- Desarrollador: Determina cómo será construido
- Tester: Explora qué podría salir mal
Estas conversaciones suceden antes de que comience la codificación, resultando en ejemplos concretos que se convierten en pruebas automatizadas.
Example Mapping
Antes de escribir escenarios, usa Example Mapping para explorar requisitos:
Tarjeta de Historia:
┌────────────────────────────────────┐
│ Como usuario │
│ Quiero retirar efectivo │
│ Para poder obtener dinero │
└────────────────────────────────────┘
Reglas (Tarjetas amarillas):
┌──────────────────────┐ ┌──────────────────────┐
│ Debe tener saldo │ │ Límite diario: $500 │
│ suficiente │ │ │
└──────────────────────┘ └──────────────────────┘
Ejemplos (Tarjetas verdes):
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ Saldo: $100 │ │ Saldo: $600 │ │ Saldo: $100 │
│ Retiro: $50 │ │ Retiro: $200 │ │ Ya retiró $400 hoy │
│ ✓ Éxito │ │ Ya retiró $350 hoy │ │ Intenta retirar │
│ │ │ Intenta retirar │ │ $200 │
│ │ │ $200 │ │ ✗ Límite diario │
│ │ │ ✗ Límite diario │ │ │
└────────────────────┘ └────────────────────┘ └────────────────────┘
Gherkin: El Lenguaje de BDD
Gherkin proporciona un formato estructurado y legible para documentar comportamiento.
Estructura Básica de Gherkin
Feature: Retiro de Efectivo
Como cliente
Quiero retirar efectivo de un ATM
Para poder acceder a mi dinero
Background:
Given el cliente tiene una cuenta válida
And el ATM tiene suficiente efectivo
Scenario: Retiro exitoso dentro del saldo
Given el cliente tiene un saldo de $100
When el cliente solicita $50
Then el efectivo debe ser dispensado
And el saldo debe ser $50
And debe proporcionarse un recibo
Scenario: Fondos insuficientes
Given el cliente tiene un saldo de $30
When el cliente solicita $50
Then el retiro debe ser rechazado
And debe mostrarse un mensaje de error "Fondos insuficientes"
And no debe dispensarse efectivo
Mejores Prácticas de Gherkin
1. Escribe Declarativamente, No Imperativamente
# Mal - Imperativo (cómo)
Scenario: Registro de usuario
Given estoy en la página de inicio
When hago clic en "Registrarse"
And lleno "email" con "user@example.com"
And lleno "password" con "Pass123!"
And lleno "confirmar password" con "Pass123!"
And hago clic en "Crear Cuenta"
Then debo ver "Bienvenido"
# Bien - Declarativo (qué)
Scenario: Registro de usuario
Given soy un visitante nuevo
When me registro con credenciales válidas
Then debo estar logueado
And debo ver un mensaje de bienvenida
El estilo declarativo:
- Se enfoca en qué, no cómo
- Permanece estable cuando la UI cambia
- Es más legible para stakeholders no técnicos
- Requiere definiciones de pasos más sofisticadas
2. Usa Scenario Outlines para Variaciones de Datos
# Mal - Escenarios repetitivos
Scenario: Login con password inválido
Given existe un usuario con email "user@example.com"
When inicio sesión con email "user@example.com" y password "wrong"
Then debo ver un error "Credenciales inválidas"
# Bien - Scenario Outline
Scenario Outline: Validación de login
Given existe un usuario con email "user@example.com" y password "Pass123!"
When inicio sesión con email "<email>" y password "<password>"
Then debo ver un error "<error>"
Examples:
| email | password | error |
| user@example.com | wrong | Credenciales inválidas |
| wrong@example.com | Pass123! | Credenciales inválidas |
| user@example.com | Pass123 | Credenciales inválidas |
| | Pass123! | Email requerido |
| user@example.com | | Password requerido |
3. Usa Background Sabiamente
Feature: Carrito de Compras
Background:
Given estoy logueado como "customer@example.com"
And mi carrito está vacío
Scenario: Agregar item al carrito
When agrego "iPhone 15" al carrito
Then mi carrito debe contener 1 item
Scenario: Agregar múltiples items
When agrego "iPhone 15" al carrito
And agrego "AirPods Pro" al carrito
Then mi carrito debe contener 2 items
4. Un Escenario, Un Comportamiento
# Mal - Probando múltiples comportamientos
Scenario: Flujo de usuario
Given estoy logueado
When veo mi perfil
Then debo ver mi nombre
When edito mi perfil
And cambio mi nombre a "Nuevo Nombre"
Then mi nombre debe estar actualizado
# Bien - Escenarios separados
Scenario: Ver perfil
Given estoy logueado
When veo mi perfil
Then debo ver mi información actual
Scenario: Actualizar nombre de perfil
Given estoy logueado
And estoy en mi página de perfil
When cambio mi nombre a "Nuevo Nombre"
Then mi perfil debe mostrar "Nuevo Nombre"
5. Usa Tags para Organización
@critical @smoke
Feature: Autenticación
@fast
Scenario: Login con credenciales válidas
Given existe un usuario
When inicio sesión con credenciales válidas
Then debo estar logueado
@slow @integration
Scenario: Login con SSO
Given SSO está configurado
When inicio sesión vía Google
Then debo estar logueado
@wip
Scenario: Autenticación de dos factores
# Trabajo en progreso
Ejecutar tags específicos:
cucumber --tags "@smoke and not @wip"
cucumber --tags "@critical or @smoke"
Cucumber: El Campeón Java/JavaScript
Cucumber es el framework BDD más ampliamente usado, con fuerte soporte para Java, JavaScript y Ruby.
Implementación Cucumber Java
Definiciones de pasos:
public class AuthenticationSteps {
private User user;
private LoginPage loginPage;
private DashboardPage dashboardPage;
private String errorMessage;
@Given("existe un usuario con email {string} y password {string}")
public void existeUsuarioConEmailYPassword(String email, String password) {
user = UserFactory.createUser(email, password);
userRepository.save(user);
}
@When("inicio sesión con email {string} y password {string}")
public void inicioSesionConEmailYPassword(String email, String password) {
loginPage = new LoginPage(driver);
try {
dashboardPage = loginPage.login(email, password);
} catch (LoginException e) {
errorMessage = e.getMessage();
}
}
@Then("debo estar logueado")
public void deboEstarLogueado() {
assertThat(dashboardPage).isNotNull();
assertThat(sessionManager.isLoggedIn()).isTrue();
}
@Then("debo ver un error {string}")
public void deboVerUnError(String expectedError) {
assertThat(errorMessage).contains(expectedError);
}
@After
public void cleanup() {
if (user != null) {
userRepository.delete(user);
}
sessionManager.logout();
}
}
Tablas de datos para datos complejos:
Scenario: Registrar usuario con perfil
When me registro con los siguientes detalles:
| email | name | age | city |
| john@example.com | John Doe | 30 | New York |
Then el usuario debe ser creado
@When("me registro con los siguientes detalles:")
public void meRegistroConLosSiguientesDetalles(DataTable dataTable) {
Map<String, String> data = dataTable.asMap(String.class, String.class);
User user = User.builder()
.email(data.get("email"))
.name(data.get("name"))
.age(Integer.parseInt(data.get("age")))
.city(data.get("city"))
.build();
registrationService.register(user);
}
Cucumber JavaScript (con Playwright)
// features/support/world.js
const { setWorldConstructor, Before, After } = require('@cucumber/cucumber') (como se discute en [Serenity BDD Integration: Living Documentation and Advanced Test Reporting](/blog/serenity-bdd-integration));
const { chromium } = require('playwright');
class CustomWorld {
async init() {
this.browser = await chromium.launch();
this.context = await this.browser.newContext();
this.page = await this.context.newPage();
}
async cleanup() {
await this.page?.close();
await this.context?.close();
await this.browser?.close();
}
}
setWorldConstructor(CustomWorld);
Before(async function() {
await this.init();
});
After(async function() {
await this.cleanup();
});
// features/step_definitions/authentication.steps.js
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('@playwright/test');
Given('existe un usuario con email {string}', async function(email) {
this.user = await createUser({ email, password: 'Test123!' });
});
When('inicio sesión con email {string} y password {string}',
async function(email, password) {
await this.page.goto('http://localhost:3000/login');
await this.page.fill('[name="email"]', email);
await this.page.fill('[name="password"]', password);
await this.page.click('button[type="submit"]');
}
);
Then('debo estar logueado', async function() {
await this.page.waitForURL('**/dashboard');
const userMenu = await this.page.locator('[data-testid="user-menu"]');
await expect(userMenu).toBeVisible();
});
SpecFlow: BDD para .NET
SpecFlow trae BDD estilo Cucumber al ecosistema .NET.
Feature: Autenticación de Usuario
Como usuario
Quiero acceder de forma segura a mi cuenta
Para poder gestionar mis datos
Scenario: Login con credenciales válidas
Given existe un usuario con email "user@example.com"
When inicio sesión con credenciales válidas
Then debo ser redirigido al dashboard
And debo ver un mensaje de bienvenida
[Binding]
public class AuthenticationSteps
{
private readonly ScenarioContext _scenarioContext;
private readonly IUserService _userService;
private User _user;
public AuthenticationSteps(
ScenarioContext scenarioContext,
IUserService userService)
{
_scenarioContext = scenarioContext;
_userService = userService;
}
[Given(@"existe un usuario con email ""(.*)""")]
public async Task ExisteUnUsuarioConEmail(string email)
{
_user = await _userService.CreateUserAsync(new User
{
Email = email,
Password = "Test123!",
Name = "Test User"
});
_scenarioContext["User"] = _user;
}
[When(@"inicio sesión con credenciales válidas")]
public async Task InicioSesionConCredencialesValidas()
{
var user = _scenarioContext.Get<User>("User");
await _loginPage.LoginAsync(user.Email, "Test123!");
}
[AfterScenario]
public async Task Cleanup()
{
if (_user != null)
{
await _userService.DeleteUserAsync(_user.Id);
}
}
}
Behave: BDD para Python
Behave trae BDD a Python con una API limpia y Pythonic.
# features/authentication.feature
Feature: Autenticación de Usuario
Background:
Given la aplicación está ejecutándose
Scenario: Login exitoso
Given existe un usuario con email "user@example.com"
When inicio sesión con email "user@example.com" y password "Test123!"
Then debo estar logueado
And debo ver mi dashboard
# features/steps/authentication_steps.py
from behave import given, when, then
from selenium.webdriver.common.by import By
@given('la aplicación está ejecutándose')
def step_app_running(context):
context.driver.get('http://localhost:3000')
@given('existe un usuario con email "{email}"')
def step_user_exists(context, email):
context.user = create_user(
email=email,
password='Test123!',
name='Test User'
)
context.users_to_cleanup.append(context.user.id)
@when('inicio sesión con email "{email}" y password "{password}"')
def step_login(context, email, password):
context.driver.get('http://localhost:3000/login')
email_field = context.driver.find_element(By.NAME, 'email')
password_field = context.driver.find_element(By.NAME, 'password')
submit_button = context.driver.find_element(
By.CSS_SELECTOR, 'button[type="submit"]'
)
email_field.send_keys(email)
password_field.send_keys(password)
submit_button.click()
@then('debo estar logueado')
def step_should_be_logged_in(context):
wait = WebDriverWait(context.driver, 10)
wait.until(EC.url_contains('/dashboard'))
Configuración de entorno (hooks):
# features/environment.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def before_feature(context, feature):
if 'webdriver' in feature.tags:
chrome_options = Options()
chrome_options.add_argument('--headless')
context.driver = webdriver.Chrome(options=chrome_options)
def before_scenario(context, scenario):
context.users_to_cleanup = []
def after_scenario(context, scenario):
for user_id in context.users_to_cleanup:
delete_user(user_id)
if scenario.status == 'failed' and hasattr(context, 'driver'):
screenshot_name = f"screenshots/{scenario.name}.png"
context.driver.save_screenshot(screenshot_name)
def after_feature(context, feature):
if hasattr(context, 'driver'):
context.driver.quit()
Documentación Viva
Uno de los mayores beneficios de BDD es la documentación viva que permanece sincronizada con el comportamiento real del sistema.
Generando Reportes
Reportes HTML de Cucumber:
# Generar JSON de Cucumber
mvn test -Dcucumber.plugin="json:target/cucumber.json"
# Generar reporte HTML
npx cucumber-html-reporter \
--jsonFile=target/cucumber.json \
--output=target/cucumber-report.html
Reportes Allure (funciona con Cucumber, SpecFlow, Behave):
# Ejecutar tests con Allure
mvn test -Dcucumber.plugin="io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"
# Generar y servir reporte
allure serve target/allure-results
Integración CI/CD
Las pruebas BDD deben ser parte de tu pipeline de integración continua.
GitHub Actions
# .github/workflows/bdd-tests.yml
name: BDD Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
- name: Run BDD tests
run: mvn test -Dcucumber.filter.tags="@smoke"
- name: Generate Allure Report
if: always()
run: mvn allure:report
- name: Publish test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: target/cucumber-reports
Ejecución Paralela
<!-- pom.xml -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<parallel>methods</parallel>
<threadCount>4</threadCount>
<perCoreThreadCount>true</perCoreThreadCount>
</configuration>
</plugin>
Errores Comunes de BDD y Cómo Evitarlos
1. Sobre-Especificación
# Mal - demasiado detallado, frágil
Scenario: Buscar productos
Given estoy en la página de inicio
When hago clic en el icono de búsqueda en la esquina superior derecha
And escribo "laptop" en el cuadro de búsqueda con id "search-input"
And presiono la tecla Enter
Then debo ver un spinner de carga por 2 segundos
And debo ver 15 productos mostrados en diseño de cuadrícula
# Bien - se enfoca en comportamiento
Scenario: Buscar productos
Given estoy en la página de inicio
When busco "laptop"
Then debo ver resultados de productos relevantes
2. Escenarios que Dependen Entre Sí
# Mal - escenarios dependen de previos
Scenario: Crear usuario
When creo usuario "john@example.com"
Then el usuario debe ser creado
Scenario: Actualizar usuario # Asume que el escenario previo corrió
When actualizo usuario "john@example.com" nombre a "John Doe"
Then el nombre debe estar actualizado
# Bien - cada escenario es independiente
Scenario: Actualizar nombre de usuario
Given existe un usuario con email "john@example.com"
When actualizo el nombre del usuario a "John Doe"
Then el nombre del usuario debe ser "John Doe"
Conclusión
BDD transforma cómo los equipos colaboran en el desarrollo de software. Conclusiones clave:
- Comienza con conversaciones: La discusión de los Tres Amigos es más importante que las herramientas
- Escribe declarativamente: Enfócate en qué, no en cómo
- Elige la herramienta correcta: Cucumber para JVM/JS, SpecFlow para .NET, Behave para Python
- Mantén documentación viva: Deja que tus tests sirvan como docs siempre actualizadas
- Integra con CI/CD: Automatiza ejecución y reportes
- Evita errores comunes: Mantén escenarios independientes, enfocados en comportamiento y declarativos
BDD bien hecho crea un entendimiento compartido en el equipo, reduce el retrabajo y asegura que lo que se construye es lo que realmente se necesitaba. Los escenarios se convierten tanto en especificación como en verificación, eliminando la brecha entre requisitos y pruebas.
Comienza pequeño, enfócate en escenarios de alto valor y expande gradualmente tu práctica de BDD. La inversión en comunicación clara y entendimiento compartido paga dividendos a lo largo del ciclo de vida del desarrollo.