Integración CI/CD
Integración CI/CD
Esta sección cubre cómo poner Gherkin a correr automáticamente en pipelines, con paralelización, retries, gating y reportes accesibles para todo el equipo.
Workflow base: GitHub Actions + Cucumber-JS
# .github/workflows/bdd.yml
name: BDD Tests
on:
push:
branches: [main]
pull_request:
jobs:
bdd-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx cucumber-js --tags "@smoke and not @wip"
- uses: actions/upload-artifact@v4
if: always()
with:
name: cucumber-report-smoke
path: reports/
bdd-regression:
runs-on: ubuntu-latest
needs: bdd-smoke
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx cucumber-js --tags "@regression and not @wip"
- uses: actions/upload-artifact@v4
if: always()
with:
name: cucumber-report-regression
path: reports/
Patrón: smoke en cada push (rápido), regression solo en PRs (completo).
Paralelización
Cucumber-JS soporta paralelización built-in:
cucumber-js --parallel 4
Ejecuta cuatro workers en paralelo. Cada worker toma scenarios independientes.
Requisitos para paralelizar
- Scenarios independientes (no asumen orden ni estado compartido)
- DB con schema fresh por worker (o tests usan UUIDs únicos)
- Browser con contexts aislados (Playwright crea contextos limpios)
- Mocks/feature flags scoped por worker o por scenario
Con behave: behavex o behave-parallel
pip install behavex
behavex --parallel-processes 4 --parallel-scheme scenario
--parallel-scheme scenario paraleliza por scenario (recomendado).
Sharding entre runners
Para tests muy largos, distribuir scenarios entre varias máquinas:
# .github/workflows/bdd.yml
jobs:
bdd:
strategy:
matrix:
shard: [1, 2, 3, 4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx cucumber-js --parallel 2 --shard ${{ matrix.shard }}/4
Cucumber-JS no tiene --shard nativo todavía. Una solución: distribuir features manualmente:
jobs:
bdd-auth:
runs-on: ubuntu-latest
steps:
- run: npx cucumber-js features/auth/
bdd-cart:
runs-on: ubuntu-latest
steps:
- run: npx cucumber-js features/cart/
bdd-billing:
runs-on: ubuntu-latest
steps:
- run: npx cucumber-js features/billing/
Retry de scenarios flaky
Algunos tests son inestables (race conditions, network glitches). Cucumber-JS soporta --retry:
cucumber-js --retry 2
Reintenta hasta 2 veces los scenarios que fallan. Útil pero anti-patrón si lo aplicás a todo: enmascara tests realmente flaky.
Mejor: marcá los flaky con tag y retry solo esos.
cucumber-js --retry 2 --retry-tag-filter "@flaky"
@flaky
Scenario: Sometimes fails due to external API
...
Y mantené una lista visible de scenarios @flaky con un tracker para arreglarlos.
Gating por etapas
Estrategia clásica: smoke → integration → e2e en cascada.
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- run: npx cucumber-js --tags "@smoke"
integration:
runs-on: ubuntu-latest
needs: smoke
steps:
- run: npx cucumber-js --tags "@integration and not @smoke"
e2e:
runs-on: ubuntu-latest
needs: integration
if: github.event_name == 'pull_request'
steps:
- run: npx cucumber-js --tags "@e2e"
Si smoke falla, no corre el resto. Ahorra recursos y feedback rápido.
Tags por entorno
Distintos entornos pueden requerir distintos tests:
| Entorno | Tags ejecutados |
|---|---|
| local (dev) | @smoke and not @wip and not @manual |
| CI (PR) | @smoke and @regression and not @wip |
| staging | @e2e and not @local-only |
| production | @production-safe and not @destructive |
- run: npx cucumber-js --tags "${{ env.TAG_EXPRESSION }}"
env:
TAG_EXPRESSION: "@smoke and not @wip"
Servicios externos: docker-compose en CI
Si tus tests necesitan DB, queue, redis, etc:
jobs:
bdd:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx cucumber-js
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/test
GitHub Actions levanta el contenedor antes de los tests.
Reportes en PR comments
Postear el resumen en el PR para visibilidad:
- name: BDD report comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const report = JSON.parse(fs.readFileSync('reports/cucumber.json', 'utf-8'))
let total = 0, passed = 0, failed = 0
for (const f of report) {
for (const s of f.elements ?? []) {
total++
const ok = s.steps.every(st => st.result?.status === 'passed')
if (ok) passed++; else failed++
}
}
const body = `## BDD Report\n${passed}/${total} scenarios passed (${failed} failed)`
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
Publicar living docs en GitHub Pages
deploy-living-docs:
runs-on: ubuntu-latest
needs: bdd
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cucumber-report-regression
path: dist
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
Cada merge a main regenera y publica los reports en GitHub Pages.
GitLab CI
Equivalente para GitLab:
# .gitlab-ci.yml
stages:
- test
variables:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
bdd:smoke:
stage: test
image: node:20
services:
- postgres:16
script:
- npm ci
- npx cucumber-js --tags "@smoke and not @wip"
artifacts:
when: always
paths:
- reports/
reports:
junit: reports/junit.xml
variables:
DATABASE_URL: postgres://test:test@postgres:5432/test
bdd:regression:
extends: bdd:smoke
script:
- npm ci
- npx cucumber-js --tags "@regression"
only:
- merge_requests
Behave en CI (Python)
# .github/workflows/bdd-python.yml
name: BDD Tests Python
on: [push, pull_request]
jobs:
bdd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: behave --tags=@smoke --tags=~@wip --format=junit --junit-directory=reports
- uses: actions/upload-artifact@v4
if: always()
with:
name: behave-report
path: reports/
Métricas y dashboard de equipo
Mantenés un dashboard con métricas que importan:
- Pass rate semanal y por sprint
- Tiempo total de la suite (no debe crecer descontroladamente)
- Scenarios flaky (frecuencia de fallo intermitente)
- Cobertura por REQ-ID (qué requisitos no tienen scenarios)
- Cobertura por feature (qué features tienen más/menos scenarios)
Herramientas:
- Allure: dashboard built-in con trends
- Cucumber Reports: dashboard cloud oficial
- Grafana + Prometheus: para equipos con stack propio
- Datadog/New Relic: para equipos con APM
Anti-patrones de CI/CD
1. Suite que tarda 45 minutos
Acción: paralelizar y filtrar por tags. Suite > 10 minutos pierde valor de feedback rápido.
2. Reintentar todo automáticamente
Acción: marcar @flaky y atacar la causa raíz.
3. Ignorar tests rotos
Acción: si un scenario está roto > 1 día, taggéalo @quarantined, abrí un ticket y arreglálo en sprint actual o eliminálo.
4. Reportes que solo CI ve
Acción: publicar como living docs en GitHub Pages o similar.
5. Sin gating en deploys
Acción: bloquear merges a main si los smoke tests fallan; bloquear deploy a producción si los e2e fallan.
Checklist de pipeline maduro
- Smoke tests en cada commit (< 2 min)
- Regression en PRs (< 15 min con paralelización)
- E2E en pre-release o nightly
- Servicios (DB, queue) levantados en CI
- Paralelización (
--parallel) - Tags filtrando por etapa
- Artifacts subidos siempre (success o fail)
- PR comment con resumen
- Living docs publicadas
- Métricas de pass rate / duración tracked
- Scenarios
@flakyen lista visible y siendo atacados
Diagrama: pipeline completo
flowchart TD
Commit[git push] --> SmokeJob[Smoke job<br/>< 2 min]
SmokeJob -->|pasa| RegressionJob[Regression<br/>solo en PRs<br/>< 15 min]
SmokeJob -->|falla| Block[Bloquear merge]
RegressionJob -->|pasa| MergeOK[Merge habilitado]
MergeOK --> Main[Merge a main]
Main --> E2EJob[E2E nightly<br/>contra staging]
E2EJob -->|pasa| Deploy[Deploy a prod]
E2EJob -->|falla| Alert[Alerta al equipo]
Main --> LivingDocs[Regenerar living docs<br/>en GitHub Pages]
Resumen
- Pipeline en cascada: smoke → regression → e2e
- Paralelización con
--parallel - Sharding para suites grandes
- Retry solo en
@flaky(no en todo) - Servicios via docker-compose o services keyword
- Reportes subidos como artifacts y publicados como living docs
- Gating en
mainy prod
Cierre del tutorial
Recorriste Gherkin de cero a hero: anatomía, primer escenario, Background, Outline, tags, Data Tables, step definitions en TS y Python, hooks, antipatrones, living docs y CI. Combinado con EARS para los requisitos, tenés un flujo completo de specification by example end-to-end.
Próximos pasos:
- Aplicá Gherkin a una feature real en tu proyecto
- Configurá el pipeline en GitHub Actions / GitLab CI
- Publicá living docs para tu equipo
- Combiná con EARS para trazabilidad requisito → scenario → código
- Ampliá a otras herramientas: Playwright, Selenium, integration testing en pytest
Gherkin es una inversión en comunicación tanto como en testing. Equipos que lo adoptan bien hablan el mismo idioma — y eso vale más que cualquier mejora puntual en cobertura.