Pruebas unitarias con el paquete testing

Intermedio
Go
Go
Actualizado: 03/04/2026

El paquete testing y la convención _test.go

En Go, las pruebas forman parte del proyecto y conviven con el código de producción. Los ficheros de prueba terminan obligatoriamente en _test.go y el compilador los excluye de los binarios finales.

Testing en Go: estructura de pruebas, API de testing.T y comandos

Para crear una prueba, declara una función que comience por Test, reciba *testing.T y no devuelva ningún valor:

// calculadora/suma_test.go
package calculadora

import "testing"

func TestSumar(t *testing.T) {
    resultado := Sumar(2, 3)
    if resultado != 5 {
        t.Errorf("Sumar(2, 3) = %d; esperado 5", resultado)
    }
}

Ejecuta con:

go test ./calculadora/
go test -v ./...          # salida detallada
go test -run TestSumar    # ejecutar solo esta prueba

Métodos de *testing.T

| Método | Comportamiento | |--------|----------------| | t.Error(msg) | Marca como fallida, continúa la prueba | | t.Errorf(fmt, ...) | Igual que Error con formato | | t.Fatal(msg) | Marca como fallida y detiene la prueba | | t.Fatalf(fmt, ...) | Igual que Fatal con formato | | t.Log(msg) | Muestra mensaje solo con -v | | t.Skip(msg) | Omite la prueba (útil para integración) | | t.Helper() | Marca la función actual como helper |

Subtests con t.Run

Los subtests permiten nombrar y agrupar casos relacionados:

func TestMultiplicar(t *testing.T) {
    t.Run("positivos", func(t *testing.T) {
        if Multiplicar(3, 4) != 12 {
            t.Fail()
        }
    })

    t.Run("por cero", func(t *testing.T) {
        if Multiplicar(5, 0) != 0 {
            t.Fail()
        }
    })

    t.Run("negativos", func(t *testing.T) {
        if Multiplicar(-2, 3) != -6 {
            t.Fail()
        }
    })
}

Ejecutar solo un subtest: go test -run TestMultiplicar/por_cero

Table-driven tests: el patrón idiomático

El patrón más común en proyectos Go es definir una tabla de casos de prueba y recorrerla con un bucle:

func TestDividir(t *testing.T) {
    casos := []struct {
        nombre    string
        a, b      float64
        esperado  float64
        debeError bool
    }{
        {"division normal", 10, 2, 5, false},
        {"division por cero", 5, 0, 0, true},
        {"negativos", -6, 3, -2, false},
        {"resultado decimal", 7, 2, 3.5, false},
    }

    for _, tc := range casos {
        t.Run(tc.nombre, func(t *testing.T) {
            resultado, err := Dividir(tc.a, tc.b)

            if tc.debeError {
                if err == nil {
                    t.Error("se esperaba error pero la función tuvo éxito")
                }
                return
            }

            if err != nil {
                t.Fatalf("error inesperado: %v", err)
            }
            if resultado != tc.esperado {
                t.Errorf("Dividir(%.1f, %.1f) = %.1f; esperado %.1f",
                    tc.a, tc.b, resultado, tc.esperado)
            }
        })
    }
}

Helpers con t.Helper()

Cuando extraes lógica de aserción a funciones auxiliares, usa t.Helper() para que los fallos apunten a la línea del test que llamó al helper, no al interior del helper:

func assertIgual(t *testing.T, obtenido, esperado int) {
    t.Helper()
    if obtenido != esperado {
        t.Errorf("obtenido %d, esperado %d", obtenido, esperado)
    }
}

func TestCalculadora(t *testing.T) {
    assertIgual(t, Sumar(1, 1), 2)   // el fallo apunta aquí
    assertIgual(t, Sumar(0, 0), 0)
}

Setup y teardown

Go no tiene decoradores ni hooks de ciclo de vida. El patrón idiomático es usar TestMain para configuración global o funciones de limpieza con t.Cleanup:

func TestConBD(t *testing.T) {
    db := abrirBDPruebas(t)
    t.Cleanup(func() {
        db.Close()          // se ejecuta al finalizar el test
    })

    // usar db...
}

func abrirBDPruebas(t *testing.T) *sql.DB {
    t.Helper()
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("no se pudo abrir BD de pruebas: %v", err)
    }
    return db
}

Cobertura de código

go test -cover ./...
go test -coverprofile=cobertura.out ./...
go tool cover -html=cobertura.out   # abre informe en el navegador

Un porcentaje de cobertura alto no garantiza la calidad de las pruebas, pero es un indicador útil de áreas sin probar.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Go es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Go

Explora más contenido relacionado con Go y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la estructura de un fichero de pruebas en Go (_test.go).
  • Escribir funciones de prueba con el tipo *testing.T.
  • Usar t.Error, t.Fatal y t.Log para reportar resultados.
  • Organizar pruebas con subtests usando t.Run.
  • Aplicar el patrón table-driven tests para múltiples casos de prueba.
  • Ejecutar pruebas con go test y sus flags principales (-v, -run, -count).