Tu primer escenario
Tu primer escenario
Hora de ver Gherkin en acción. Vamos a:
- Crear un proyecto vacío con TypeScript
- Instalar Cucumber-JS
- Escribir un feature
- Implementar step definitions
- Ejecutar y ver el resultado
Setup del proyecto
mkdir gherkin-tour && cd gherkin-tour
npm init -y
npm install --save-dev @cucumber/cucumber typescript ts-node @types/node
Estructura mínima:
gherkin-tour/
├── features/
│ ├── calculator.feature
│ └── step_definitions/
│ └── calculator.steps.ts
├── cucumber.cjs
├── tsconfig.json
└── package.json
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true
}
}
cucumber.cjs
module.exports = {
default: {
requireModule: ['ts-node/register'],
require: ['features/step_definitions/**/*.ts'],
paths: ['features/**/*.feature'],
format: ['progress-bar', 'html:cucumber-report.html']
}
}
El feature: features/calculator.feature
Feature: Calculadora básica
Como usuario de la calculadora, quiero sumar y restar números
para hacer aritmética simple.
Scenario: Sumar dos números positivos
Given una calculadora vacía
When sumo 2 y 3
Then el resultado es 5
Scenario: Restar dos números
Given una calculadora vacía
When resto 10 menos 7
Then el resultado es 3
Las step definitions: features/step_definitions/calculator.steps.ts
import { Given, When, Then } from '@cucumber/cucumber'
import { strict as assert } from 'node:assert'
class Calculator {
private result = 0
reset() { this.result = 0 }
add(a: number, b: number) { this.result = a + b }
subtract(a: number, b: number) { this.result = a - b }
getResult() { return this.result }
}
let calc: Calculator
Given('una calculadora vacía', function () {
calc = new Calculator()
})
When('sumo {int} y {int}', function (a: number, b: number) {
calc.add(a, b)
})
When('resto {int} menos {int}', function (a: number, b: number) {
calc.subtract(a, b)
})
Then('el resultado es {int}', function (expected: number) {
assert.equal(calc.getResult(), expected)
})
Ejecutar
Agregá al package.json:
{
"scripts": {
"test": "cucumber-js"
}
}
Y ejecutá:
npm test
Salida esperada:
..........
2 scenarios (2 passed)
6 steps (6 passed)
0m00.123s
Report: cucumber-report.html
Tu primer escenario Gherkin está ejecutándose como test automatizado.
Cómo se conectan feature y steps
flowchart LR
F[".feature"] -->|cucumber parsea| Steps[Lista de steps]
Steps -->|cada step matchea| Pattern[Pattern en step definition]
Pattern --> Code[Función TypeScript ejecuta]
Code --> Assert{¿Pasa el assert?}
Assert -->|sí| Next[Siguiente step]
Assert -->|no| Fail[Fail del scenario]
- Cucumber lee el
.feature - Por cada step, busca una función registrada con
Given/When/Thencuyo patrón coincida - Si encuentra match, ejecuta la función
- Si todas las funciones pasan, el scenario pasa
Parámetros: {int}, {string}, {float}
Cucumber soporta cucumber-expressions para extraer valores:
When('sumo {int} y {int}', (a: number, b: number) => { ... })
When('agrego {string} al carrito', (sku: string) => { ... })
When('aplico descuento del {float}%', (pct: number) => { ... })
Y placeholders custom (lo veremos más adelante).
¿Y si falta una step definition?
Si el .feature tiene un step sin matching definition:
1) Scenario: Sumar dos números positivos
✗ Given una calculadora vacía
⚠ When sumo 2 y 3 (undefined)
Implement with the following snippet:
When('sumo {int} y {int}', function (int, int2) {
return 'pending';
});
Cucumber te da el código snippet listo para copiar. Workflow típico:
- Escribir el
.feature - Correr → ver los snippets faltantes
- Copiar y rellenar los snippets en step definitions
- Correr → debería pasar
Esto se llama outside-in development o specification first.
Versión Python (behave)
Para los que prefieren Python:
pip install behave
Estructura:
gherkin-tour-py/
├── features/
│ ├── calculator.feature
│ └── steps/
│ └── calculator.py
Mismo .feature. Step definitions en features/steps/calculator.py:
from behave import given, when, then
class Calculator:
def __init__(self):
self.result = 0
def add(self, a, b):
self.result = a + b
def subtract(self, a, b):
self.result = a - b
@given('una calculadora vacía')
def step_empty_calculator(context):
context.calc = Calculator()
@when('sumo {a:d} y {b:d}')
def step_add(context, a, b):
context.calc.add(a, b)
@when('resto {a:d} menos {b:d}')
def step_subtract(context, a, b):
context.calc.subtract(a, b)
@then('el resultado es {expected:d}')
def step_result(context, expected):
assert context.calc.result == expected, f"esperado {expected}, obtuve {context.calc.result}"
Ejecutar:
behave
Salida:
Feature: Calculadora básica # features/calculator.feature:1
Scenario: Sumar dos números positivos # features/calculator.feature:6
Given una calculadora vacía # features/steps/calculator.py:10
When sumo 2 y 3 # features/steps/calculator.py:14
Then el resultado es 5 # features/steps/calculator.py:22
Scenario: Restar dos números # features/calculator.feature:11
Given una calculadora vacía # features/steps/calculator.py:10
When resto 10 menos 7 # features/steps/calculator.py:18
Then el resultado es 3 # features/steps/calculator.py:22
1 feature passed, 0 failed, 0 skipped
2 scenarios passed, 0 failed, 0 skipped
6 steps passed, 0 failed, 0 skipped
Notá la diferencia clave entre los dos parsers:
- Cucumber-JS usa cucumber-expressions:
{int},{string} - behave usa Python format-style parsing:
{a:d},{name:S}(también acepta regex)
Diferencias resumidas TS vs Python
| Aspecto | Cucumber-JS (TS) | behave (Python) |
|---|---|---|
| Anotación | Given(...), When(...) | @given(...), @when(...) |
| Capturas | {int}, {string} | {name:d}, {name} |
| Estado compartido | Variables module-scope o World | context parameter |
| Hooks | Before, After decorators | before_scenario, after_scenario en environment.py |
| Comando ejecución | cucumber-js | behave |
Los conceptos son idénticos. Cambia la sintaxis.
¿Qué hicimos?
- ✅ Setup mínimo de un proyecto Cucumber (TS y Python)
- ✅ Primer feature con dos scenarios
- ✅ Step definitions que mapean a código
- ✅ Tests pasando
Esto es Gherkin “Hello World”. El resto del tutorial agrega Backgrounds, Scenario Outlines, tags, data tables, hooks, antipatrones e integración CI.
En el siguiente capítulo vemos cómo reutilizar precondiciones con Background.