Pruebas en Go: Unit, Integration, E2E — guía completa
Las pruebas son una parte esencial del desarrollo de software de calidad. En el lenguaje Go (Golang), las pruebas están integradas a nivel de la biblioteca estándar, lo que las hace simples y eficientes. En este artículo, analizaremos los tres niveles principales de pruebas: unitarias (unit), de integración (integration) y de extremo a extremo (e2e). Aprenderás cómo escribirlas, cuándo aplicarlas y qué herramientas utilizar.
¿Por qué son tan importantes las pruebas en Go?
Go fue creado con un enfoque en la simplicidad y la confiabilidad. El paquete integrado testing y la herramienta go test permiten agregar fácilmente pruebas a cualquier proyecto. El equipo de Go sigue la filosofía: "Si el código no está probado, no funciona". Las pruebas ayudan a:
- Detectar errores en las primeras etapas del desarrollo
- Simplificar la refactorización y el mantenimiento del código
- Documentar el comportamiento de funciones y módulos
- Aumentar la confianza en la estabilidad de la aplicación
Pruebas unitarias en Go
Las pruebas unitarias verifican el funcionamiento de funciones, métodos o estructuras individuales de forma aislada de las dependencias externas. En Go, se escriben en archivos con el sufijo _test.go.
Fundamentos de las pruebas unitarias
Veamos un ejemplo simple. Supongamos que tenemos una función de suma:
// math.gopackage main
func Add(a, b int) int { return a + b}La prueba para esta función se vería así:
// math_test.gopackage main
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) }}Ejecución de la prueba: go test -v
Pruebas basadas en tablas (Table-driven tests)
Go fomenta el uso de pruebas basadas en tablas para verificar múltiples escenarios:
func TestAddTableDriven(t *testing.T) { tests := []struct { name string a, b int want int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -1, -2, -3}, {"zero", 0, 0, 0}, {"mixed", -5, 10, 5}, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Add(tt.a, tt.b) if got != tt.want { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) } }) }}Pruebas con mocks
Para aislar las pruebas unitarias, a menudo se necesitan mocks. En Go se pueden usar interfaces y bibliotecas como gomock o testify/mock.
// Definimos la interfaztype UserRepository interface { GetUser(id int) (User, error)}
// Prueba usando testifyfunc TestGetUserName(t *testing.T) { mockRepo := new(MockUserRepository) mockRepo.On("GetUser", 1).Return(User{Name: "Alice"}, nil)
service := NewUserService(mockRepo) name, err := service.GetUserName(1)
assert.NoError(t, err) assert.Equal(t, "Alice", name) mockRepo.AssertExpectations(t)}Pruebas de integración en Go
Las pruebas de integración verifican la interacción entre componentes del sistema: bases de datos, APIs, sistemas de archivos y otros servicios externos. En Go, es común colocarlas en paquetes separados o usar build tags.
Pruebas con base de datos
Para probar el trabajo con bases de datos, a menudo se usan testcontainers o bases de datos temporales:
// integration_test.go (con build tag)//go:build integration
package integration
import ( "database/sql" "testing" _ "github.com/lib/pq")
func TestUserRepository(t *testing.T) { // Conexión a la BD de prueba db, err := sql.Open("postgres", "host=localhost port=")
```