Marcos Ramírez BETA
Diagrama de especificaciones técnicas junto a código fuente

Spec Driven Development: Gherkin y las alternativas que importan

· ⏱ 12+ min lectura

Robert C. Martin (sí, el del Clean Code) lleva semanas publicando en X sobre un tema que muchos daban por muerto: las especificaciones ejecutables.

No es nostalgia. Es otra cosa.

El auge de los asistentes de Inteligencia Artificial para escribir código ha resucitado un problema que los que llevamos años en esto conocemos bien: si no defines lo que quieres, la Inteligencia Artificial inventa. Y lo que inventa puede pasar los tests, compilar, funcionar incluso. Pero no hace lo que tenía que hacer.

Ahí entra SDD. Spec Driven Development.

¿Qué es SDD exactamente?

SDD no es TDD. No es BDD. Es un paso más arriba en la cadena de abstracción.

En TDD escribes un test que falla, luego código que lo pasa, luego refactorizas. El test es una especificación a nivel de unidad: este método, con esta entrada, produce esta salida.

En BDD escribes escenarios en lenguaje casi natural con Given-When-Then. La especificación describe comportamiento desde la perspectiva del usuario.

SDD propone algo distinto: la especificación no es el test ni el escenario. La especificación es el contrato. El código se deriva de ella. Siempre.

Dicho de otro modo:

  • TDD especifica el cómo.
  • BDD especifica el qué.
  • SDD especifica el qué, el cómo no, las restricciones, los bordes, las invariantes, y verifica que todo se cumple en tiempo de compilación y ejecución.

Osea, no es que escribas los tests antes del código. Es que escribes la especificación antes de todo, y de ella se derivan los tests, el código, la documentación, y los contratos entre servicios.

Gherkin: el rey indiscutible (y sus problemas)

Si hablamos de lenguajes de especificación, Gherkin es el que todo el mundo conoce. Lo usa Cucumber y sus derivados en casi cualquier lenguaje.

Feature: Retirada de efectivo
  Scenario: Saldo suficiente
    Given una cuenta con 100€ de saldo
    When el cliente retira 30€
    Then el saldo final debe ser 70€
    And el cajero debe entregar 30€

Es legible. Es entendible por no técnicos. Y durante años ha sido la respuesta estándar a “cómo especificamos el comportamiento”.

Pero tiene problemas.

El primero: el mantenimiento. Los ficheros .feature crecen, se duplican, se desincronizan del código. El paso de Given-When-Then a código real requiere step definitions que acaban siendo otro lugar donde meter bugs.

El segundo: el salto semántico. Un PO puede escribir un escenario en Gherkin, pero alguien tiene que implementar los step definitions. Y ahí se pierde la trazabilidad. El PO escribe “Given una cuenta con 100€”, el desarrollador implementa givenUnaCuentaConSaldo(100), y si los nombres cambian, la especificación original queda como documentación muerta.

El tercero: Cucumber no escala bien. He visto proyectos con cientos de escenarios Gherkin donde ejecutar la suite completa lleva horas. Y donde encontrar qué escenario falla requiere paciencia de monje budista.

Alternativas a Gherkin que merecen la pena

Aquí van las que conozco y he usado. Cada una resuelve los problemas de Gherkin de forma distinta.

RSpec (Ruby)

RSpec no es Gherkin. Es un framework de testing, pero su sintaxis describe / context / it + let lo convierte en un lenguaje de especificación en sí mismo:

describe Cuenta do
  let(:cuenta) { Cuenta.new(saldo: 100) }

  describe "#retirar" do
    context "con saldo suficiente" do
      it "reduce el saldo" do
        cuenta.retirar(30)
        expect(cuenta.saldo).to eq(70)
      end
    end
  end
end

La diferencia con Gherkin es que aquí no hay step definitions: el código ES la especificación. Y eso mantiene la trazabilidad forzada. Si cambia la especificación, cambia el test. No hay un fichero .feature que se pueda quedar obsoleto.

Fitnesse

Fitnesse es el hijo de Ward Cunningham, de la época de WikiWikiWeb. Es anterior a Cucumber y mucha gente no lo conoce, pero es una bestia.

Funciona con tablas. Escribes la especificación en una tabla HTML renderizada por un wiki, y el framework ejecuta las celdas contra tu código:

|import|
|com.miempresa.dominio|

|script: Cajero|
|crearCuenta|100|
|retirar|30|devolver?|
|70|

Cada celda se traduce a una llamada a un método. Sin step definitions intermedios. Sin Gherkin. Sin archivos sueltos.

El problema de Fitnesse: es un wiki. Tiene su propio servidor, su propia base de datos, su propia forma de organizar las páginas. En equipos grandes se convierte en un caos de páginas huérfanas y permisos mal configurados.

Concordion

Concordion es como un término medio entre Gherkin y Fitnesse. Escribes las especificaciones en HTML (o Markdown) con marcadores especiales:

<p>Dada una cuenta con <span c:set="#saldo">100</span>€,
al retirar <span c:execute="#resultado = retirar(#saldo, 30)">30€</span>,
el saldo debe ser <span c:assertEquals="#resultado">70</span>€.</p>

La gracia de Concordion es que el HTML se ve como documentación. Puedes abrirlo en un navegador y leerlo como un documento de requisitos. Y al mismo tiempo es ejecutable.

El problema: mezclar marcadores de especificación con HTML es tedioso. Y el HTML renderizado como documentación queda bien, pero cuando tienes 200 especificaciones, la navegación se complica.

Approval Tests

Approval Tests (o Golden Master Testing) no es un lenguaje de especificación, sino una técnica. En lugar de escribir asserts, capturas la salida de tu sistema y la comparas con una “approved version” guardada en el repo.

def test_genera_factura():
    factura = generar_factura(cliente="Marcos", importe=100)
    verify(factura)

La primera vez que se ejecuta, se guarda el resultado como approved. Las siguientes veces, si el resultado cambia, el test falla y tienes que revisar si el cambio es correcto y actualizar el approved.

Para especificar comportamiento complejo donde el output es difícil de definir con asserts tradicionales, esto es una maravilla. El problema: si cambia algo estructural, todas las approved files se rompen a la vez.

Property-Based Testing

Property-Based Testing (QuickCheck en Haskell, Hypothesis en Python) invierte la lógica: en lugar de decir “para esta entrada concreta, espero esta salida”, dices “para cualquier entrada que cumpla estas propiedades, la salida debe cumplir estas invariantes”:

@given(cuentas_con_saldo_positivo())
def test_retirar_nunca_deja_saldo_negativo(cuenta, cantidad):
    saldo_anterior = cuenta.saldo
    if cantidad <= saldo_anterior:
        cuenta.retirar(cantidad)
        assert cuenta.saldo == saldo_anterior - cantidad

Esto no sustituye a Gherkin ni a los escenarios concretos. Es un nivel distinto de especificación: el de las invariantes. Y combinado con escenarios concretos, es brutal.

Robert C. Martin y el renacimiento de SDD

Vale, aquí va lo que me ha llevado a escribir este post.

Robert C. Martin ha estado publicando en X (@unclebobmartin) sobre lo que él llama Spec Driven Design. No es un concepto nuevo (él mismo habla de ATDD, Acceptance Test Driven Development, desde hace años), pero lo ha aplicado a un contexto nuevo: el desarrollo asistido por Inteligencia Artificial.

Su tesis, resumida en varios hilos de X de principios de 2026, es esta:

Cuando le pides a una Inteligencia Artificial que escriba código, la Inteligencia Artificial optimiza para que los tests pasen. Si solo tienes tests unitarios, la Inteligencia Artificial escribe código que pasa los tests unitarios. Pero ese código puede violar la arquitectura, ignorar los contratos entre servicios, no cumplir los requisitos funcionales reales, y en general, hacer chapuzas que parecen correctas.

La solución que propone son dos líneas de defensa:

  1. Tests de aceptación (specs ejecutables): escritos en Gherkin o en el lenguaje de especificación que elijas, describen el comportamiento esperado del sistema desde fuera. La Inteligencia Artificial no puede tocarlos. Son una restricción externa que no puede modificar.

  2. Tests unitarios: escritos por la Inteligencia Artificial junto con el código, verifican la corrección interna.

La clave está en que los tests de aceptación no son negociables. Los escribe el humano. La Inteligencia Artificial solo puede implementar para que pasen.

En uno de sus posts, Martin dice algo que me parece clave:

“The two different streams of tests cause the AI to think much more deeply about the structure of the code. It can’t just willy-nilly plop code around and write a unit test for it.”

Traducción libre: “Los dos streams de tests obligan a la Inteligencia Artificial a pensar mucho más profundamente sobre la estructura del código. No puede simplemente esparcir código a lo loco y escribir un test unitario para justificarlo.”

Y añade:

“Specs will be co-authored by the humans and the AI, but with final approval, ferociously defended, by the humans.”

En su proyecto empire-2025, Martin implementó esto con Gherkin como lenguaje de especificación, pero con una diferencia clave: su pipeline no es Cucumber al uso. Es lo que él llama “a strange hybrid of Cucumber and the test fixtures”: un parser/generador que conoce el sistema a fondo y produce tests completos y ejecutables sin necesidad de step definitions manuales.

El pipeline: Gherkin → JSON IR (intermediate representation) → tests generados → runner. Y además, mutation testing como sidecar para verificar la calidad de los tests.

De eso ha estado hablando en X durante los últimos meses. Y ha generado un movimiento interesante: hay implementaciones del enfoque para Claude Code (el plugin atdd), para herramientas de Inteligencia Artificial en general, y cada vez más gente redescubriendo que las especificaciones, bien hechas, son la clave para que la Inteligencia Artificial genere código que no haya que reescribir.

El pipeline completo, según su propio tweet

El 1 de junio de 2026, Martin publicó un hilo en X (@unclebobmartin/status/2061482997610610863) donde describe su pipeline de principio a fin. Vale la pena leerlo entero, pero el resumen es este:

  1. Specs informales escritas a mano. Las escribe él. Sin formato. Sin Gherkin. Intención pura.
  2. Agente especificador. Convierte esas notas en especificaciones más formales y las subdivide en tareas. Martin las revisa.
  3. Generación de Gherkin. Cada tarea se pasa a un agente que genera Gherkin, lo poda, y lo prepara para el siguiente paso. Martin hace spot checks.
  4. Agente coder. Escribe los tests de aceptación directamente desde el Gherkin. Luego los tests unitarios. Luego el código. Todo en ese orden.
  5. Agente refactorer. Reduce el crap a 6 o menos, elimina duplicación, escribe property tests y los hace pasar.
  6. Agente architect. Ejecuta mutation testing sobre el lenguaje, cubriendo secciones sin cubrir y matando supervivientes. Luego mutation testing sobre el Gherkin.
  7. Suite completa. Cuando todo pasa, el resultado vuelve al especificador, coder y refactorer para la siguiente iteración.
  8. Spot check humano. Martin revisa el código resultante.

Su descripción del proceso merece citarse textual:

“I start with very informal specifications written by hand. I have an agent convert these into harder specifications that are subdivided into tasks. I review these. Then I feed those tasks into the specifier agent, which converts each task to Gherkin, prunes the Gherkin, and then hands it off to the coder agent. I spot check the Gherkin.”

Y el cierre del hilo es casi poético para los que llevamos años en esto:

“This is an exercise of transformations from the informal to the formal through managed stages, with human interaction decreasing with each stage.”

La limitación, dice, no es la calidad del código generado: “Raw computer power is the limiting factor. Those mutation tests are CPU intensive.”

Osea, el problema no es que la Inteligencia Artificial escriba mal código. El problema es que los ordenadores no son lo bastante rápidos para ejecutar todas las verificaciones que Martin quiere ponerle. Eso, siendo realistas, es un problema bonito.

Este pipeline concreto ha inspirado implementaciones como el plugin atdd para Claude Code y el proyecto Acceptance Pipeline Specification que Martin publicó en GitHub, donde formaliza el pipeline como un estándar portable: Gherkin → JSON IR → tests generados → runner, con mutation testing como sidecar.

Un ejemplo práctico

Pongamos que quieres implementar un sistema de descuentos. En lugar de decirle a la Inteligencia Artificial “implementa descuentos por fidelidad”, escribes una especificación ejecutable:

Scenario: Cliente fiel con descuento acumulado
  Given un cliente con 5 compras en los últimos 30 días
  And un precio base de 100€
  When se aplica el descuento por fidelidad
  Then el descuento debe ser del 10%
  And el precio final debe ser 90€

Scenario: Cliente nuevo sin descuento
  Given un cliente con 0 compras
  And un precio base de 100€
  When se aplica el descuento por fidelidad
  Then el descuento debe ser del 0%
  And el precio final debe ser 100€

Con Gherkin tradicional, necesitas step definitions. Con el pipeline de SDD de Martin, el parser genera el código de test directamente desde la especificación, sin paso intermedio. La Inteligencia Artificial implementa la lógica de negocio para que esos tests pasen, y no puede saltárselos porque son la definición del contrato.

Si usas Property-Based Testing en lugar de Gherkin, la especificación sería:

@given(clientes_con_distintas_antiguedades())
def test_descuento_por_fidelidad(cliente, precio_base):
    descuento = calcular_descuento(cliente, precio_base)
    if cliente.compras_recientes >= 5:
        assert descuento == precio_base * 0.1
    else:
        assert descuento == 0

Cada enfoque tiene su momento. Gherkin para cuando necesitas que un no-técnico valide los escenarios. Property-Based para cuando las invariantes son más importantes que los casos concretos.

¿Cuál elegir?

No hay una respuesta universal, pero después de años probando cosas, mi criterio es este:

  • Si trabajas con domain experts no técnicos que necesitan validar el comportamiento, Gherkin es lo menos malo. Acepta sus limitaciones y busca herramientas que automaticen la generación de step definitions.

  • Si tu equipo es técnico y quieres trazabilidad forzada, RSpec (o su equivalente en tu lenguaje) es mejor que Gherkin. El código es la especificación.

  • Si necesitas documentación viviente que se lea como documentación, Concordion.

  • Si trabajas con sistemas legacy o outputs complejos (facturas, informes, PDFs), Approval Tests.

  • Si tu dominio tiene invariantes fuertes (matemáticas financieras, motores de búsqueda, validación de datos), Property-Based Testing.

  • Si usas Inteligencia Artificial generativa para escribir código, el enfoque de Robert C. Martin: tests de aceptación como contrato no negociable, pipeline de especificación ejecutable, y mutation testing como verificación.

Y la regla de oro: no mezcles enfoques en el mismo proyecto. Elegir uno y ser disciplinado es mejor que tener tres sistemas de especificación que nadie mantiene.

Compártelo si te ha resultado útil.

¿Qué enfoque usas tú para especificar comportamiento? ¿Gherkin, algo más raro, o puro TDD sin más? Cuéntame.

Y… que las especificaciones te acompañen.

Artículos relacionados

Código Python de scraping recolocándose sobre una estructura web que cambia

Scrapling: el scraper de Python que se repara cuando la web cambia

Scrapling es una librería de scraping para Python con una idea que llevábamos años pidiendo: cuando la web cambia su HTML, el scraper se recoloca solo en vez de romperse. Tres fetchers, bypass de Cloudflare, un parser 784 veces más rápido que BeautifulSoup y un framework de spiders incluido. Te cuento qué hace bien, dónde tiene truco y cuándo NO deberías usarlo.

08:30 7 min Marcos Ramírez Lucía