Data Tables y Doc Strings

Por: Artiko
gherkindata-tablesdoc-stringsestructuras

Data Tables y Doc Strings

Algunos steps necesitan pasar más que un par de strings: tablas de registros, JSON, queries SQL, HTML. Gherkin ofrece Data Tables y Doc Strings para eso.

Data Tables

Una tabla pegada debajo de un step se pasa al step definition como objeto de tipo DataTable.

Given los siguientes productos en el catálogo:
  | sku   | name      | price | stock |
  | P-100 | Mouse     | 25    | 100   |
  | P-101 | Keyboard  | 80    | 50    |
  | P-102 | Webcam    | 120   | 20    |

La tabla es parte del step “Given los siguientes productos en el catálogo:”. El step definition la recibe como segundo argumento.

Cucumber-JS

import { Given, DataTable } from '@cucumber/cucumber'

Given('los siguientes productos en el catálogo:', function (table: DataTable) {
  // table.hashes() devuelve un array de objetos:
  // [{ sku: 'P-100', name: 'Mouse', price: '25', stock: '100' }, ...]
  const products = table.hashes()

  for (const row of products) {
    this.catalog.add({
      sku: row.sku,
      name: row.name,
      price: Number(row.price),
      stock: Number(row.stock),
    })
  }
})

Métodos útiles de DataTable:

MétodoDevuelve
table.hashes()Array de objetos (primera fila = headers)
table.rows()Array de arrays (sin headers)
table.raw()Array de arrays (con headers, todo as-is)
table.rowsHash()Objeto donde col 0 = key, col 1 = value (vertical)

behave

from behave import given

@given('los siguientes productos en el catálogo')
def step_seed_products(context):
    for row in context.table:
        context.catalog.add(
            sku=row['sku'],
            name=row['name'],
            price=float(row['price']),
            stock=int(row['stock']),
        )

context.table es iterable; cada row se accede como dict.

Patrones comunes con Data Tables

1. Tabla horizontal (lista de registros)

Given los siguientes usuarios:
  | email             | role  | status   |
  | [email protected]   | user  | active   |
  | [email protected]   | admin | active   |
  | [email protected]   | user  | inactive |
Given('los siguientes usuarios:', function (table: DataTable) {
  for (const row of table.hashes()) {
    this.users.create(row)
  }
})

Útil para sembrar múltiples registros.

2. Tabla vertical (un objeto con varios campos)

When envío la solicitud de cliente con:
  | field      | value                |
  | first_name | Ana                  |
  | last_name  | García               |
  | email      | [email protected]      |
  | phone      | +54 9 11 1234 5678   |
When('envío la solicitud de cliente con:', function (table: DataTable) {
  const data = table.rowsHash()
  // { first_name: 'Ana', last_name: 'García', email: '...', phone: '...' }
  this.response = await this.api.post('/clients', data)
})

Útil para formularios con muchos campos.

3. Tabla de aserciones

Then el reporte de ventas contiene:
  | category    | total |
  | electronics | 1250  |
  | books       | 340   |
  | clothing    | 800   |
Then('el reporte de ventas contiene:', function (table: DataTable) {
  const expected = table.hashes()
  for (const row of expected) {
    const actual = this.report.findCategory(row.category)
    assert.equal(actual.total, Number(row.total))
  }
})

Útil para validar múltiples valores en una sola aserción legible.

Doc Strings — texto multilínea

Para pasar texto largo (JSON, SQL, HTML, prosa) usá triple comilla doble:

When envío POST /clients con body:
  """
  {
    "first_name": "Ana",
    "last_name": "García",
    "email": "[email protected]",
    "address": {
      "street": "Av. Corrientes 1234",
      "city": "Buenos Aires",
      "country": "AR"
    }
  }
  """

Cucumber-JS

When('envío POST {string} con body:', function (path: string, body: string) {
  this.response = await this.api.post(path, JSON.parse(body))
})

El parámetro body recibe el string completo del Doc String.

behave

@when('envío POST "{path}" con body')
def step_post_with_body(context, path):
    body = json.loads(context.text)
    context.response = context.api.post(path, body=body)

context.text contiene el Doc String.

Content type hints en Doc Strings

Cucumber 7+ permite anotar el content type:

When envío POST /clients con body:
  """json
  {
    "name": "Ana"
  }
  """

When ejecuto la query:
  """sql
  SELECT id, email FROM users WHERE status = 'active'
  """

El content type aparece en reportes con syntax highlighting. No cambia comportamiento, solo documentación.

Cuándo Data Table vs Doc String

Usar Data Table cuando…Usar Doc String cuando…
Estructura es tabular (filas/cols)El payload es libre (JSON, XML, SQL)
Múltiples registros del mismo tipoUn solo objeto/blob
Querés que la spec sea legibleEl formato exacto importa (raw JSON)
Necesitás iterarNecesitás parsear como un todo

Anti-patrón 1: tablas con tipos mezclados

- Given el siguiente usuario:
-   | field    | value                                                |
-   | email    | [email protected]                                       |
-   | profile  | {"age": 30, "hobbies": ["reading"]}                  ⚠ JSON dentro de celda

Mejor partir: una tabla para campos planos y un Doc String para el JSON anidado, o usar dos steps:

+ Given el siguiente usuario:
+   | field   | value           |
+   | email   | [email protected] |
+ And con el perfil:
+   """json
+   { "age": 30, "hobbies": ["reading"] }
+   """

Anti-patrón 2: Doc Strings muy largos

When envío:
- """
- (300 líneas de JSON o XML)
- """

Si el payload es grande, guardalo en un fixture y referencialo:

When envío POST /import con el archivo "fixtures/big-payload.json"

El step lee el archivo:

When('envío POST {string} con el archivo {string}', async function (path: string, fixturePath: string) {
  const content = await fs.readFile(fixturePath, 'utf-8')
  this.response = await this.api.post(path, JSON.parse(content))
})

Anti-patrón 3: tablas como reemplazo de Outline

- Given los siguientes scenarios:
-   | input  | expected |
-   | "abc"  | weak     |
-   | "Abc1" | medium   |
- Then todos pasan

Eso es un Outline:

+ Scenario Outline: Validación de password strength
+   Given input "<input>"
+   Then strength es "<expected>"
+   Examples:
+     | input  | expected |
+     | abc    | weak     |
+     | Abc1   | medium   |

Data Tables son input para un step. Outlines son plantillas para scenarios completos.

Ejemplo completo con ambas estructuras

Feature: Importar pedidos desde archivo

  Scenario: Importar pedido con productos múltiples
    Given el siguiente cliente:
      | field      | value             |
      | id         | C-001             |
      | email      | [email protected] |
    And los siguientes productos disponibles:
      | sku   | price |
      | P-100 | 25    |
      | P-101 | 80    |
    When envío POST /orders con body:
      """json
      {
        "customer_id": "C-001",
        "lines": [
          { "sku": "P-100", "quantity": 2 },
          { "sku": "P-101", "quantity": 1 }
        ]
      }
      """
    Then la respuesta tiene status 201
    And el cuerpo contiene:
      | field    | value |
      | order_id |       |
      | total    | 130   |
      | status   | new   |

Tres estructuras combinadas:

Resumen

En el siguiente capítulo profundizamos en step definitions en TypeScript.