Saltar al contenido principal
eLearner.app
Módulo 9 · Lección 2 de 542/50 en el curso~14 min
Lecciones del módulo (2/5)

Pruebas basadas en tablas

Las pruebas basadas en tablas son el patrón idiomático de Go: en lugar de escribir N funciones TestThisSpecificCase separadas, se declara una porción de casos y se itera. Hay UN bloque de aserción y los datos de prueba están separados de la lógica.

El patrón base

Go
func TestAbs(t *testing.T) {
    cases := []struct {
        name string
        in   int
        want int
    }{
        {"pos", 3, 3},
        {"neg", -3, 3},
        {"zero", 0, 0},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Abs(tc.in); got != tc.want {
                t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
            }
        })
    }
}

Tres ingredientes:

  1. Porción de estructura anónima con los campos habituales: name, las entradas, la salida esperada (y tal vez un error esperado).
  2. for _, tc := range cases: el ciclo de ejecución.
  3. t.Run(tc.name, func(t *testing.T) { ... }) — cada caso se convierte en una subprueba con su propio nombre.

¿Por qué t.Run?

t.Run crea una subprueba que tiene:

  • Nombre individual mostrado en la salida: TestAbs/pos, TestAbs/neg, ...
  • Su propio t *testing.T: un fallo en un caso no detiene los demás (a menos que se utilice t.Fatal).
  • Filtrado de línea de comando: go test -run TestAbs/neg ejecuta solo ese caso.
  • Paralelismo opcional: llame a t.Parallel() dentro de la función para paralelizar.
Bash
go test -v -run TestAbs/neg

Casos con errores esperados

Go
cases := []struct {
    name    string
    in      string
    want    int
    wantErr bool
}{
    {"ok", "42", 42, false},
    {"bad", "x", 0, true},
}
for _, tc := range cases {
    t.Run(tc.name, func(t *testing.T) {
        got, err := Atoi(tc.in)
        if (err != nil) != tc.wantErr {
            t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
        }
        if !tc.wantErr && got != tc.want {
            t.Errorf("got %d, want %d", got, tc.want)
        }
    })
}

El problema del cierre del bucle (antes de Go 1.22)

En versiones anteriores a Go 1.22, la variable tc se compartió entre iteraciones: llamar a t.Parallel() dentro de t.Run provocaba que todas las subpruebas se ejecutaran en el mismo último caso. La solución fue:

Go
for _, tc := range cases {
    tc := tc // local rebind (pre-1.22)
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

Nombrar los casos

Mejores prácticas:

  • Un nombre corto pero evocador: "empty input", "too large", "negative".
  • Sin espacios si quieres filtrar fácilmente con -run (o usar guiones bajos).
  • Incluya siempre un caso de "camino feliz", un caso de "borde" (cero, vacío) y al menos un caso de error.

Ejercicios

Ejercicio#go.m9.l2.e1
Intentos: 0Cargando...

Defina una porción de casos de estructura anónima con campos en y deseo, completa con 3 casos para probar Abs (positivo, negativo, cero).

Cargando editor...

Solución disponible después de 3 intentos

Ejercicio#go.m9.l2.e2
Intentos: 0Cargando...

Agregue t.Run con tc.name para convertir cada caso en una subprueba con nombre.

Cargando editor...

Solución disponible después de 3 intentos

Cuestionario#go.m9.l2.e3
Listo

¿Cuál es la principal ventaja del patrón basado en tablas sobre N funciones de prueba separadas?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
Opciones de respuesta