go generate es un comando que ejecuta programas arbitrarios a partir de directivas especiales en los comentarios del código fuente. Su propósito es automatizar la generación de código: métodos String() para tipos enumerados, mocks de interfaces, código SQL tipado y código de Protocol Buffers. Todo esto como parte del proceso de build.
Cómo funciona go generate
//go:generate comando argumentos
Al ejecutar go generate ./..., Go busca estas directivas en los ficheros .go del proyecto y ejecuta los comandos. Habitualmente se concentran en un fichero generate.go en la raíz del paquete.
stringer: métodos String() para tipos enumerados
// estado.go
package domain
//go:generate stringer -type=EstadoPedido -output=estado_string.go
type EstadoPedido int
const (
EstadoPendiente EstadoPedido = iota
EstadoProcesando
EstadoEnviado
EstadoCancelado
)
// Después de go generate, puedes usar:
// fmt.Println(EstadoPendiente) ? "EstadoPendiente"
// e.String() ? "EstadoEnviado"
go install golang.org/x/tools/cmd/stringer@latest go generate ./internal/domain/
El fichero estado_string.go generado contiene un array constante de cadenas con una búsqueda O(1). Es más eficiente que un switch manual y se mantiene sincronizado automáticamente al agregar o quitar valores del enum.
mockgen: mocks automáticos de interfaces
// generate.go
package ports
//go:generate mockgen -source=repositories.go -destination=../mocks/mock_repositories.go -package=mocks
// En los tests:
import (
"testing"
"github.com/golang/mock/gomock"
"tuapp/internal/mocks"
)
func TestCrearUsuario(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mocks.NewMockUsuarioRepository(ctrl)
mockEmail := mocks.NewMockEmailService(ctrl)
// Configurar expectativas
mockRepo.EXPECT().
BuscarPorEmail("[email protected]").
Return(nil, nil)
mockRepo.EXPECT().
Guardar(gomock.Any()).
Return(nil)
uc := application.NewCrearUsuarioUseCase(mockRepo, mockEmail)
out, err := uc.Ejecutar(application.CrearUsuarioInput{
Nombre: "Ana",
Email: "[email protected]",
})
if err != nil {
t.Fatalf("error inesperado: %v", err)
}
if out.Email != "[email protected]" {
t.Errorf("email incorrecto: %s", out.Email)
}
}
sqlc: queries SQL tipadas
sqlc lee tus queries SQL y genera código Go tipado para cada una. Nunca más interface{} en las respuestas de base de datos:
-- queries/usuarios.sql -- name: ObtenerUsuario :one SELECT id, nombre, email, activo FROM usuarios WHERE id = ? LIMIT 1; -- name: ListarUsuarios :many SELECT id, nombre, email FROM usuarios WHERE activo = true ORDER BY nombre LIMIT ? OFFSET ?; -- name: CrearUsuario :execresult INSERT INTO usuarios (nombre, email, activo, creado_en) VALUES (?, ?, true, NOW());
// generate.go
//go:generate sqlc generate
// El código generado:
type ObtenerUsuarioRow struct {
ID int32
Nombre string
Email string
Activo bool
}
func (q *Queries) ObtenerUsuario(ctx context.Context, id int32) (ObtenerUsuarioRow, error) {
row := q.db.QueryRowContext(ctx, obtenerUsuario, id)
var i ObtenerUsuarioRow
err := row.Scan(&i.ID, &i.Nombre, &i.Email, &i.Activo)
return i, err
}
buf: generar protobuf y gRPC
// generate.go del paquete proto
//go:generate buf generate
// buf.gen.yaml
version: v2
plugins:
- plugin: buf.build/protocolbuffers/go
out: gen/go
opt: paths=source_relative
- plugin: buf.build/grpc/go
out: gen/go
opt: paths=source_relative
Organizar las directivas en generate.go
La convención es crear un fichero generate.go (o doc.go) en cada paquete que necesite generación. Esto agrupa todas las directivas en un lugar visible y permite ejecutar go generate ./... desde la raíz:
// internal/ports/generate.go // Package ports define las interfaces del dominio. // Para regenerar los mocks ejecuta: go generate ./... package ports //go:generate mockgen -source=repositories.go -destination=../mocks/mock_repositories.go -package=mocks //go:generate mockgen -source=services.go -destination=../mocks/mock_services.go -package=mocks
En el CI añade un paso que ejecuta go generate ./... y luego comprueba con git diff --exit-code que no hay ficheros generados sin commitear. Esto garantiza que el código generado siempre está sincronizado con el código fuente.
