Introducción: ¿Por qué las pruebas en Go son una necesidad?
\nGo (Golang) es un lenguaje de programación moderno, creado para construir sistemas confiables y escalables. Sin embargo, incluso el código más elegante no está exento de errores. Es por eso que las pruebas son una parte integral del desarrollo en Go. El paquete incorporado testing proporciona un potente conjunto de herramientas para escribir pruebas unitarias, pruebas de integración e incluso verificaciones end-to-end (e2e).
En este artículo, analizaremos todos los niveles de pruebas en Go: desde pruebas unitarias simples hasta escenarios e2e complejos. Aprenderá cómo estructurar correctamente las pruebas, usar mocks, escribir pruebas table-driven y configurar pruebas de integración con dependencias reales. El material será útil tanto para principiantes como para desarrolladores experimentados que deseen sistematizar sus conocimientos.
\n\n1. Pruebas unitarias en Go: lo fundamental
\nLas pruebas unitarias verifican el funcionamiento de funciones o métodos individuales de forma aislada de las dependencias externas. En Go, se utiliza el paquete estándar testing para esto. Las reglas principales: los archivos de prueba deben llamarse *_test.go y las funciones de prueba deben comenzar con Test.
1.1. Prueba unitaria simple
\nConsideremos una función de suma de dos números:
\n// math.go\npackage math\n\nfunc Add(a, b int) int {\n return a + b\n}\nPrueba para ella:
\n// math_test.go\npackage math\n\nimport "testing"\n\nfunc TestAdd(t *testing.T) {\n result := Add(2, 3)\n expected := 5\n if result != expected {\n t.Errorf("Add(2, 3) = %d; want %d", result, expected)\n }\n}\nEjecución de pruebas: go test ./... o go test -v para salida detallada.
1.2. Pruebas Table-driven (pruebas tabulares)
\nLa comunidad de Go utiliza activamente el enfoque table-driven para verificar múltiples casos:
\nfunc TestAddTableDriven(t *testing.T) {\n tests := []struct {\n name string\n a, b int\n want int\n }{\n {"positive", 2, 3, 5},\n {"negative", -1, -2, -3},\n {"zero", 0, 0, 0},\n }\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n if got := Add(tt.a, tt.b); got != tt.want {\n t.Errorf("Add() = %d, want %d", got, tt.want)\n }\n })\n }\n}\nEste enfoque hace que las pruebas sean fáciles de leer y ampliar.
\n\n1.3. Pruebas con errores
\nPara funciones que devuelven errores, use t.Fatal o t.Errorf:
func TestDivide(t *testing.T) {\n _, err := Divide(10, 0)\n if err == nil {\n t.Fatal("expected error for division by zero")\n }\n}\n\n2. Pruebas de integración: trabajo con dependencias
\nLas pruebas de integración verifican la interacción de componentes: bases de datos, API, sistema de archivos. En Go, es común separarlas de las pruebas unitarias mediante build tags o una estructura de paquetes separada.
\n\n2.1. Uso de build tags
\nCree un archivo con el tag //go:build integration al inicio:
//go:build integration\n\npackage db\n\nimport (\n "testing"\n "database/sql"\n _ "github.com/lib/pq"\n)\n\nfunc TestPostgresConnection(t *testing.T) {\n dsn := "host=localhost port=5432 user=test password=test dbname=test sslmode=disable"\n db, err := sql.Open("postgres", dsn)\n if err != nil {\n t.Fatalf("failed to open db: %v", err)\n }\n defer db.Close()\n \n if err := db.Ping(); err != nil {\n t.Fatalf("failed to ping db: %v", err)\n }\n}\nEjecución solo de pruebas de integración: go test -tags=integration ./...
2.2. Mocks y stubs
\nPara aislar las pruebas, use interfaces y mocks. Ejemplo con la popular biblioteca gomock:
// user.go\ntype UserRepository interface {\n GetUser(id int) (*Us