Antipatrones Gherkin

Por: Artiko
gherkinantipatroneserrores-comunescalidad-bdd

Antipatrones Gherkin

Escribir Gherkin sintácticamente correcto es fácil. Escribir Gherkin que sirva como living documentation y test mantenible requiere disciplina. Esta sección lista los antipatrones más frecuentes.

1. Imperativo en lugar de declarativo

Imperativo (mal) — describe pasos mecánicos de UI:

Scenario: Login del usuario
  Given el usuario abre el navegador
  And navega a "https://app.example.com/login"
  And espera 2 segundos
  When ingresa "[email protected]" en el campo con id "email"
  And ingresa "secret123" en el campo con id "password"
  And hace clic en el botón con id "submit"
  And espera 1 segundo
  Then la URL es "https://app.example.com/dashboard"
  And el elemento con id "user-greeting" contiene "Hola Ana"

Declarativo (bien) — describe qué hace el usuario, no cómo:

Scenario: Usuario obtiene acceso al dashboard con credenciales válidas
  Given un usuario registrado con email "[email protected]"
  When inicia sesión con email "[email protected]" y password "secret123"
  Then ve el dashboard
  And el dashboard saluda a "Ana"

Por qué: el imperativo se rompe con cada cambio de UI (id renombrado, espera distinta, página redirige diferente). El declarativo solo se rompe si el comportamiento cambia.

Las step definitions absorben los detalles técnicos:

When('inicia sesión con email {string} y password {string}', async function (email, password) {
  await this.page.goto('/login')
  await this.page.fill('#email', email)
  await this.page.fill('#password', password)
  await this.page.click('#submit')
  await this.page.waitForURL(/.*\/dashboard/)
})

2. UI-coupled

Mal:

When hago clic en el cuarto link del segundo menú de la sidebar

Bien:

When abro la sección de configuración de notificaciones

Si reordenan los links o renombran “sidebar” → “rail”, el feature sigue funcionando. La step definition cambia.

3. Scenarios dependientes

Mal:

Scenario: Crear usuario
  When creo un usuario "[email protected]"
  Then existe el usuario "[email protected]"

Scenario: Login con el usuario recién creado
  When envío POST /auth/login con email "[email protected]"   ⚠ asume estado del scenario anterior
  Then la respuesta tiene status 200

Bien:

Scenario: Crear usuario
  When creo un usuario "[email protected]"
  Then existe el usuario "[email protected]"

Scenario: Login con usuario existente
  Given un usuario registrado con email "[email protected]" y password "secret123"
  When envío POST /auth/login con email "[email protected]" y password "secret123"
  Then la respuesta tiene status 200

Cada scenario debe ser independiente. Si necesitás el setup, declarálo explícitamente.

4. Scenarios gigantes

Mal: 30 steps en un scenario.

Scenario: Flujo completo de e-commerce
  Given un usuario registrado
  When ...
  And ...
  And ...
  And ...
  And ...
  (25 steps más)
  Then ...

Bien: partir en scenarios enfocados.

Scenario: Agregar productos al carrito
  ...

Scenario: Aplicar cupón de descuento
  ...

Scenario: Completar checkout
  ...

Scenario: Recibir email de confirmación
  ...

Regla: 3-10 steps por scenario es ideal. Más de 15, partilo.

5. Detalles técnicos en el feature

Mal:

Scenario: Cargar productos
  Given la tabla "products" tiene la fila (id=100, sku=P-100, price=25)
  When envío SELECT * FROM products WHERE id=100
  Then la respuesta JSON tiene { "id": 100, "sku": "P-100", "price": 25.00 }

Bien:

Scenario: Consultar un producto del catálogo
  Given el producto "P-100" en el catálogo con precio 25
  When solicito el producto "P-100"
  Then recibo el producto con sku "P-100" y precio 25

Las step definitions encapsulan SQL, JSON y otros detalles. El feature habla en lenguaje de dominio.

6. Aserciones en Given o When

Mal:

Given el carrito tiene 3 productos     ⚠ ¿es precondición o aserción?
When el carrito tiene 3 productos      ⚠ esto no es una acción

Bien:

Given un carrito con 3 productos
When agrego un producto adicional
Then el carrito tiene 4 productos

7. Tres “Then” sin claridad

Mal:

Then el sistema responde
And se guarda en la base
And se envía un email

Mejor:

Then la respuesta tiene status 201
And la base de datos tiene una nueva orden con status "pending"
And se envía un email de confirmación a "[email protected]"

Las aserciones deben ser específicas y verificables.

8. Negaciones vagas

Mal:

Then el sistema no falla
And no muestra errores

Bien:

Then la respuesta tiene status 200
And el cuerpo no contiene el campo "error"
And el log no contiene entradas con nivel "ERROR"

“No falla” no es verificable. Cuantificá la negación.

9. Lógica condicional en pasos

Mal:

Given un usuario
When envía credenciales
Then si el usuario es admin ve el panel admin
And si es regular ve el dashboard

Bien: partir en dos scenarios.

Scenario: Admin ve el panel admin
  Given un usuario administrador
  When inicia sesión
  Then ve el panel admin

Scenario: Usuario regular ve el dashboard
  Given un usuario regular
  When inicia sesión
  Then ve el dashboard

10. Reutilizar steps con significado distinto

Mal:

// step definitions
Given('un usuario "{email}"', function (email) {
  this.user = createUser(email)  // a veces crea, a veces busca
})

Si distinto código en cada llamada, separá:

Given('un usuario nuevo con email {string}', function (email) {
  this.user = this.api.createUser(email)
})

Given('un usuario existente con email {string}', function (email) {
  this.user = this.db.findUser({ email })
})

El feature queda explícito.

11. Tiempos y delays mágicos

Mal:

When hago clic en "Guardar"
And espero 5 segundos
Then la lista contiene "Nuevo registro"

Bien: la step definition espera explícitamente lo que necesita, no un timeout fijo.

When('hago clic en {string}', async function (label) {
  await this.page.click(`button:has-text("${label}")`)
})

Then('la lista contiene {string}', async function (text) {
  await this.page.waitForSelector(`text="${text}"`, { timeout: 10000 })
})

El waitFor espera el resultado real. No usar sleep(5000).

12. Steps que ocultan complejidad

Mal:

When ejecuto la magia

Bien:

When proceso la orden con pago tarjeta de crédito y envío estándar

Si el step tiene un nombre vago, los lectores no entienden qué pasa. Nombrá explícitamente.

13. Tags inventados ad-hoc

Mal:

@check @temporary @do-not-skip @level-3 @important
Scenario: ...

Bien: vocabulario fijo documentado.

@auth @critical @REQ-AUTH-001 @smoke
Scenario: ...

14. Background como contenedor de basura

Mal:

Background:
  Given un usuario
  And configurar mocks
  And seed de DB
  And feature flag X enabled
  And feature flag Y disabled
  And mock del servicio de email
  And mock del servicio de pagos
  And el reloj fijado en 2024-01-15
  And ...

Bien: pasar setup técnico a Before hooks. Mantener Background solo con setup de dominio.

Background:
  Given un usuario registrado con plan "pro"

15. Feature sin descripción

Mal:

Feature: Login
  Scenario: ...

Bien:

Feature: Autenticación de usuarios

  Como sistema multiusuario, debemos permitir a los
  usuarios autenticarse antes de acceder a datos
  personales. Esta feature cubre login, logout y
  manejo de sesión.

  Scenario: ...

La descripción explica el por qué.

16. Scenarios sin propósito claro

Mal:

Scenario: Test 1
Scenario: Test 2

Bien:

Scenario: Usuario obtiene JWT con credenciales válidas
Scenario: Login con email inexistente devuelve 401

El título del scenario describe el comportamiento que verifica.

17. Asumir order de Examples

Mal:

Scenario Outline:
  When agrego "<sku>"
  Then el total es <total>
  Examples:
    | sku   | total |
    | P-100 | 25    |
    | P-101 | 105   |   # ⚠ asume que P-100 se sumó del scenario anterior

Cada fila del Outline es un scenario independiente. El segundo no acumula del primero.

18. Mockear demasiado

Mal:

Given el servicio de pagos retorna "success"
And el servicio de email retorna "delivered"
And el servicio de DB retorna 1 row
When proceso la orden
Then todo OK

Si mockeás todo, no estás testeando el sistema — estás testeando los mocks.

Mejor: usar contract tests para servicios externos, y BDD para el sistema completo o módulos de dominio. Mockear solo el límite externo.

Checklist anti-antipatrones

Antes de aprobar un feature:

En el siguiente capítulo vemos cómo aprovechar Gherkin como living documentation.