Introducción: Por qué es importante realizar pruebas en Go
Go (Golang) es un lenguaje que desde su diseño se ha enfocado en la simplicidad, el rendimiento y la confiabilidad. El paquete integrado testing y herramientas potentes como go test hacen que escribir pruebas sea una parte integral del desarrollo. Sin pruebas de calidad, incluso el código más elegante se convierte eventualmente en un sistema "frágil" donde un cambio lo rompe todo.
En este artículo, analizaremos tres niveles clave de pruebas: unitarias, de integración y e2e (de extremo a extremo). Aprenderás en qué se diferencian, cuándo aplicarlas y cómo escribir pruebas efectivas en Go. El material será útil tanto para principiantes como para desarrolladores experimentados que buscan sistematizar sus conocimientos.
1. Pruebas Unitarias
Las pruebas unitarias son la base de la pirámide de pruebas. Verifican el funcionamiento de funciones, métodos o módulos individuales de forma aislada de las dependencias externas (bases de datos, red, sistema de archivos).
Principios Básicos de las Pruebas Unitarias en Go
- Aislamiento: El código que se está probando no debe depender de servicios externos.
- Velocidad: Las pruebas unitarias se ejecutan en milisegundos.
- Determinismo: El resultado de la prueba no debe cambiar de una ejecución a otra.
Ejemplo de una Prueba Unitaria Simple
Supongamos que tenemos una función para sumar dos números:
// math.gopackage math
func Add(a, b int) int { return a + b}La prueba para esta función se vería así:
// math_test.gopackage math
import "testing"
func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5
if result != expected { t.Errorf("Add(2, 3) = %d; want %d", result, expected) }}La prueba se ejecuta con el comando: go test ./...
Pruebas Basadas en Tablas
La comunidad de Go prefiere las pruebas basadas en tablas para verificar múltiples casos:
func TestAddTable(t *testing.T) { tests := []struct { a, b, expected int }{ {1, 1, 2}, {0, 0, 0}, {-1, 1, 0}, {100, 200, 300}, }
for _, tt := range tests { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }}Consejo: Usa t.Run() para crear pruebas anidadas con nombres claros — esto mejora la legibilidad del informe.
2. Pruebas de Integración
Las pruebas de integración verifican la interacción entre los componentes del sistema: base de datos, API, servicios externos. A diferencia de las pruebas unitarias, aquí se utilizan dependencias reales (o casi reales).
Características de las Pruebas de Integración en Go
- A menudo requieren configuración del entorno (por ejemplo, una base de datos de prueba).
- Pueden usar build tags para separar las pruebas unitarias de las de integración.
- Se ejecutan más lentamente, por lo que se ejecutan por separado.
Ejemplo: Prueba de Operaciones de Base de Datos (PostgreSQL)
Creemos un archivo con el build tag integration:
// db_test.go//go:build integration
package db
import ( "database/sql" "testing")
func TestSaveUser(t *testing.T) { // Conectar a la base de datos de prueba dsn := "host=localhost user=test password=test dbname=testdb sslmode=disable" db, err := sql.Open("postgres", dsn) if err != nil { t.Fatalf("failed to connect to db: %v", err) } defer db.Close()
// Limpiar datos antes de la prueba _, _ = db.Exec("TRUNCATE TABLE users")
// Ejecutar la operación que se está probando user := User{Name: "Alice", Email: "alice@example.com"}