El comando go explicado: run, build, test, mod, fmt y vet
Guía práctica de los comandos esenciales de Go: go run, build, test, fmt, vet y mod. Todo lo que necesitas para trabajar en local y CI.

Cuando empiezas con Go viniendo de Java, Python o Node, una de las primeras cosas que notas es que no necesitas Maven, Gradle, pytest, black ni ESLint. El binario go que se instala con el lenguaje ya incluye compilador, gestor de dependencias, runner de tests, formateador, linter y generador de código. Todo en un solo comando.
Esto no es un accidente. Es una decisión de diseño deliberada. El equipo de Go apostó desde el principio por herramientas estándar integradas en el propio toolchain. Menos configuración, menos debates sobre qué herramienta usar, menos fricción al incorporar a alguien nuevo al proyecto. Si sabes Go, ya sabes cómo compilar, testear y formatear cualquier proyecto Go.
Este artículo cubre los subcomandos que vas a usar a diario: go run, go build, go test, go fmt, go vet, go mod, go generate, go install y go env. Con ejemplos prácticos, flags útiles y cómo combinarlos en un pipeline de CI.
go run: compilar y ejecutar en un paso
go run compila y ejecuta un programa Go sin generar un binario persistente. Es el equivalente a python script.py o node app.js: útil para desarrollo rápido y scripts.
go run main.goSi tu main importa otros archivos del mismo paquete, puedes pasar varios archivos o usar el patrón de directorio:
go run .
go run ./cmd/serverPasar argumentos al programa
Los argumentos después del archivo o paquete se pasan directamente al programa:
go run main.go --port 8080 --env productionFlags útiles de go run
# Mostrar los comandos que ejecuta internamente
go run -x main.go
# Compilar sin optimizaciones (útil para debugging con Delve)
go run -gcflags="-N -l" main.goCuándo NO usar go run
go run compila cada vez que lo ejecutas. No hay caché implícita del binario resultante. Para un servidor que reinicias 50 veces al día, eso es aceptable. Para un binario que vas a distribuir o desplegar, necesitas go build.
go runes para desarrollo local. Nunca lo uses en producción ni en un Dockerfile final.
go build: crear binarios
go build compila tu código y genera un binario ejecutable. Si no especificas nombre de salida, usa el nombre del módulo o del directorio.
go build -o server ./cmd/serverEl binario resultante es estático por defecto (en la mayoría de casos): no necesita runtime, no necesita que Go esté instalado en la máquina destino. Copias el binario y funciona.
Cross-compilation
Una de las mejores características de Go es la compilación cruzada. Puedes generar binarios para cualquier plataforma desde tu máquina:
# Linux AMD64 (lo típico para servidores y contenedores Docker)
GOOS=linux GOARCH=amd64 go build -o server-linux ./cmd/server
# macOS ARM (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o server-mac ./cmd/server
# Windows
GOOS=windows GOARCH=amd64 go build -o server.exe ./cmd/serverNo necesitas una máquina virtual, no necesitas Docker, no necesitas un CI especial. Solo dos variables de entorno. Esto es algo que en Java o Python directamente no existe de forma nativa.
Flags de build comunes
# Reducir tamaño del binario eliminando información de debug
go build -ldflags="-s -w" -o server ./cmd/server
# Inyectar variables en tiempo de compilación (versión, commit, fecha)
go build -ldflags="-X main.version=1.2.3 -X main.commit=$(git rev-parse HEAD)" -o server ./cmd/server
# Build completamente estático (sin dependencias CGO)
CGO_ENABLED=0 go build -o server ./cmd/server
# Ver qué comandos ejecuta internamente
go build -x -o server ./cmd/serverInyectar versión en tiempo de compilación
Un patrón muy común es definir variables en tu main.go y rellenarlas con -ldflags:
package main
import "fmt"
var (
version = "dev"
commit = "none"
date = "unknown"
)
func main() {
fmt.Printf("server %s (commit: %s, built: %s)\n", version, commit, date)
}go build -ldflags="-X main.version=1.2.3 -X main.commit=$(git rev-parse --short HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o server .Esto te da un binario que sabe exactamente qué versión es y cuándo se compiló. Muy útil para logs y para diagnóstico en producción.
go test: tests, cobertura y benchmarks
go test es el runner de tests integrado. Lee archivos *_test.go, ejecuta funciones que empiezan por Test, Benchmark o Example, y reporta resultados. Sin JUnit, sin pytest, sin configuración.
# Ejecutar tests del paquete actual
go test
# Ejecutar tests de todo el proyecto
go test ./...
# Con output detallado
go test -v ./...
# Ejecutar solo tests que coincidan con un patrón
go test -run TestCreateUser ./internal/user/Si quieres profundizar en testing, tengo un artículo dedicado a testing en Go con más detalle sobre table-driven tests, mocks y organización.
Cobertura
# Ver porcentaje de cobertura
go test -cover ./...
# Generar archivo de cobertura para inspección detallada
go test -coverprofile=coverage.out ./...
# Ver cobertura línea por línea en el navegador
go tool cover -html=coverage.out
# Ver cobertura por función
go tool cover -func=coverage.outEl output de -cover te da un porcentaje por paquete:
ok github.com/user/project/internal/user 0.012s coverage: 87.3% of statements
ok github.com/user/project/internal/auth 0.008s coverage: 92.1% of statementsBenchmarks
Go tiene soporte nativo para benchmarks. Defines funciones Benchmark* en tus archivos de test:
func BenchmarkParseConfig(b *testing.B) {
data := loadTestConfig()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ParseConfig(data)
}
}Y los ejecutas con:
# Ejecutar benchmarks
go test -bench=. ./...
# Benchmarks con información de memoria
go test -bench=. -benchmem ./...
# Solo benchmarks, sin tests normales
go test -bench=. -run=^$ ./...
# Ejecutar un benchmark concreto
go test -bench=BenchmarkParseConfig ./internal/config/Output típico:
BenchmarkParseConfig-8 1000000 1052 ns/op 256 B/op 4 allocs/opEso te dice: se ejecutó un millón de veces, cada ejecución tardó 1052 nanosegundos, asignó 256 bytes y realizó 4 allocations. Información concreta para optimizar.
Race detector
Go tiene un detector de race conditions integrado. Actívalo con -race:
go test -race ./...Esto instrumenta el código para detectar accesos concurrentes a memoria compartida. Es más lento, así que no lo uses en benchmarks, pero debería ser obligatorio en CI.
Flags útiles de go test
# Timeout global (por defecto 10 minutos)
go test -timeout 30s ./...
# Ejecutar tests en paralelo (por defecto GOMAXPROCS)
go test -parallel 4 ./...
# Desactivar caché de tests (fuerza re-ejecución)
go test -count=1 ./...
# Tests cortos (skip de tests pesados marcados con testing.Short())
go test -short ./...go fmt: el fin de los debates de formato
go fmt formatea tu código según el estilo oficial de Go. No hay configuración, no hay opciones, no hay .editorconfig ni .prettierrc. Un formato. Para todos.
# Formatear un archivo
go fmt main.go
# Formatear todo el proyecto
go fmt ./...En la práctica, la mayoría de proyectos usan gofmt (el formateador subyacente) o goimports (que además organiza los imports):
# goimports: formatea + organiza imports + añade imports faltantes
goimports -w .Por qué esto importa
En Java tienes Checkstyle, SpotBugs, Google Java Format, IntelliJ formatter, cada uno con su config. En Python tienes Black, YAPF, autopep8, isort. En JavaScript tienes Prettier, ESLint, Standard. Cada proyecto elige uno, lo configura, y siempre hay alguien que tiene el IDE configurado diferente y mete cambios de formato en los diffs.
En Go ese problema no existe. go fmt es el estándar. Punto. No hay debate sobre tabs vs spaces (tabs), no hay debate sobre dónde va la llave de apertura (misma línea), no hay debate sobre ancho máximo de línea (no hay límite forzado). El formato es parte del lenguaje.
Si tu código no está formateado con
go fmt, la comunidad lo considera incorrecto. Así de simple.
go vet: análisis estático integrado
go vet examina tu código buscando errores comunes que el compilador no detecta: argumentos mal pasados a fmt.Printf, variables de loop capturadas en goroutines, condiciones imposibles, código inalcanzable y más.
# Analizar el paquete actual
go vet
# Analizar todo el proyecto
go vet ./...Qué detecta go vet
Algunos ejemplos de lo que encuentra:
// Printf con argumentos incorrectos
fmt.Printf("user: %d", username) // vet: wrong type for %d
// Copiar un sync.Mutex (error grave de concurrencia)
var mu sync.Mutex
mu2 := mu // vet: assignment copies lock value
// Comparación imposible
if x != x { // vet: suspicious comparison
}
// Unreachable code
func foo() int {
return 1
fmt.Println("never") // vet: unreachable code
}go vet no es un linter completo como golangci-lint, pero cubre los errores más peligrosos y es rápido. En CI debería ejecutarse siempre.
go vet vs golangci-lint
go vet es un subconjunto. golangci-lint agrupa docenas de linters (incluyendo vet) y permite configurar reglas. Para un proyecto serio, usa ambos:
# En CI: primero lo rápido y estándar
go vet ./...
# Después el análisis completo
golangci-lint rungo mod: gestión de dependencias
go mod es el sistema de módulos de Go. Gestiona las dependencias de tu proyecto a través del archivo go.mod. Si vienes de otros lenguajes: go.mod es tu pom.xml, package.json o requirements.txt.
Para una guía completa sobre módulos, mira el artículo de módulos en Go. Aquí cubro los subcomandos que usarás a diario.
go mod init
Inicializa un nuevo módulo:
go mod init github.com/usuario/proyectoEsto crea un go.mod con el nombre del módulo y la versión de Go:
module github.com/usuario/proyecto
go 1.22go mod tidy
El comando que más vas a usar. Analiza tus imports, añade las dependencias que faltan al go.mod, y elimina las que ya no se usan:
go mod tidyEjecútalo después de añadir o quitar imports. En CI, una técnica común es verificar que el go.mod y go.sum estén actualizados:
go mod tidy
git diff --exit-code go.mod go.sumSi hay diferencias, alguien se olvidó de ejecutar go mod tidy antes de hacer push.
go mod download
Descarga todas las dependencias al cache local sin compilar nada:
go mod downloadÚtil en Dockerfiles para aprovechar el cache de capas:
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Primero copiamos solo go.mod y go.sum
COPY go.mod go.sum ./
RUN go mod download
# Después el código (esta capa se invalida más a menudo)
COPY . .
RUN go build -o server ./cmd/servergo mod vendor
Copia todas las dependencias a un directorio vendor/ dentro del proyecto:
go mod vendorEsto permite builds sin acceso a internet y garantiza reproducibilidad. Para compilar usando el vendor:
go build -mod=vendor -o server ./cmd/servergo mod graph y go mod why
Para depurar dependencias:
# Ver el grafo completo de dependencias
go mod graph
# Saber por qué una dependencia está en tu go.mod
go mod why github.com/lib/pqgo mod why es especialmente útil cuando ves una dependencia en go.sum y no sabes quién la trajo.
go generate: generación de código
go generate ejecuta comandos definidos en comentarios especiales dentro de tu código Go. No es un sistema de build ni un preprocesador: es un mecanismo para ejecutar herramientas que generan código Go.
//go:generate stringer -type=Status
//go:generate mockgen -source=repository.go -destination=mock_repository.go
//go:generate protoc --go_out=. --go-grpc_out=. api.protoPara ejecutar todos los generadores del proyecto:
go generate ./...Casos de uso comunes
- Enums con stringer: Genera métodos
String()para tipos basados eniota. - Mocks con mockgen: Genera implementaciones mock de interfaces para tests.
- Protocol Buffers: Genera código Go desde archivos
.proto. - Embeds SQL: Herramientas como
sqlcgeneran código Go type-safe desde queries SQL.
Buenas prácticas con go generate
- Commitea el código generado. Quien clone tu repo no debería necesitar tener
protoc,mockgenostringerinstalados para compilar. - Verifica en CI que el código generado está actualizado:
go generate ./...
git diff --exit-codeSi hay diferencias, alguien modificó el código fuente sin regenerar.
go install: instalar herramientas
go install compila e instala un binario en $GOPATH/bin (o $GOBIN si lo tienes definido). Es la forma estándar de instalar herramientas escritas en Go.
# Instalar una herramienta específica con versión
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0
# Instalar desde el proyecto actual
go install ./cmd/serverDiferencia entre go install y go build
go buildgenera el binario en el directorio actual (o donde indiques con-o).go installgenera el binario en$GOPATH/bin.
Para herramientas CLI que quieres tener disponibles globalmente, usa go install. Para tu proyecto, usa go build.
Gestionar versiones de herramientas en el proyecto
Un patrón habitual es tener un archivo tools.go con un build tag que nunca se compila, solo para que go mod tidy registre las dependencias de herramientas:
//go:build tools
package tools
import (
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "go.uber.org/mock/mockgen"
_ "golang.org/x/tools/cmd/goimports"
)Así las versiones quedan fijadas en go.mod y todo el equipo usa las mismas.
go env: entender tu entorno
go env muestra todas las variables de entorno que afectan al toolchain de Go. Es el primer comando que ejecutar cuando algo no funciona como esperas.
# Ver todas las variables
go env
# Ver una variable concreta
go env GOPATH
go env GOROOT
go env GOOS
go env GOARCH
# Ver en formato JSON
go env -jsonVariables importantes
| Variable | Qué hace |
|---|---|
GOPATH | Directorio base para dependencias y binarios instalados |
GOROOT | Directorio de instalación de Go |
GOBIN | Dónde se instalan los binarios con go install |
GOOS | Sistema operativo objetivo para compilación |
GOARCH | Arquitectura objetivo para compilación |
GOPROXY | Proxy para descargar módulos (por defecto https://proxy.golang.org) |
GONOSUMCHECK | Módulos que no se verifican en el sumdb |
CGO_ENABLED | Si se permite compilar código C (0 o 1) |
GOFLAGS | Flags que se aplican a todos los comandos go |
Modificar variables de forma persistente
# Cambiar el proxy (útil en entornos corporativos)
go env -w GOPROXY=https://goproxy.io,direct
# Desactivar CGO por defecto
go env -w CGO_ENABLED=0Estas configuraciones se guardan en $GOPATH/env y persisten entre sesiones.
Combinando comandos en CI/CD
Un pipeline de CI para un proyecto Go típico se ve así:
#!/bin/bash
set -euo pipefail
echo "=== Verificando formato ==="
gofmt -l . | tee /tmp/fmt-check
if [ -s /tmp/fmt-check ]; then
echo "ERROR: archivos sin formatear"
exit 1
fi
echo "=== Análisis estático ==="
go vet ./...
echo "=== Verificando go.mod ==="
go mod tidy
git diff --exit-code go.mod go.sum
echo "=== Verificando código generado ==="
go generate ./...
git diff --exit-code
echo "=== Tests con race detector ==="
go test -race -cover -coverprofile=coverage.out ./...
echo "=== Cobertura ==="
go tool cover -func=coverage.out
echo "=== Build ==="
CGO_ENABLED=0 go build -ldflags="-s -w" -o /tmp/app ./cmd/serverEjemplo con GitHub Actions
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Verify formatting
run: |
if [ -n "$(gofmt -l .)" ]; then
echo "Code not formatted:"
gofmt -l .
exit 1
fi
- name: Vet
run: go vet ./...
- name: Test
run: go test -race -coverprofile=coverage.out ./...
- name: Build
run: CGO_ENABLED=0 go build -ldflags="-s -w" -o app ./cmd/serverFíjate en el orden: formato primero (es instantáneo y falla rápido), después vet, después tests, después build. No hay Maven que tarda 30 segundos en arrancar, no hay npm install de 200 MB. Un proyecto Go mediano pasa CI en menos de un minuto.
Tabla comparativa: Go tooling vs otros ecosistemas
| Tarea | Go | Java | Python | Node.js |
|---|---|---|---|---|
| Compilar | go build | mvn package / gradle build | N/A (interpretado) | N/A (interpretado) |
| Ejecutar | go run | java -jar / mvn exec:java | python script.py | node app.js |
| Tests | go test | JUnit + Maven/Gradle | pytest / unittest | Jest / Vitest |
| Formatear | go fmt | google-java-format / Spotless | Black / YAPF | Prettier |
| Linter | go vet + golangci-lint | Checkstyle / SpotBugs | Ruff / Flake8 / Pylint | ESLint |
| Dependencias | go mod | Maven / Gradle | pip / Poetry / uv | npm / pnpm / yarn |
| Cobertura | go test -cover | JaCoCo | coverage.py / pytest-cov | c8 / istanbul |
| Benchmarks | go test -bench | JMH | pytest-benchmark | Benchmark.js |
| Cross-compile | GOOS=x GOARCH=y go build | GraalVM native-image (limitado) | No nativo | No nativo (pkg) |
| Generar código | go generate | Annotation processors | No estándar | No estándar |
La diferencia principal: en Go todo es un único binario con una interfaz consistente. En otros ecosistemas necesitas instalar, configurar y mantener herramientas separadas para cada tarea.
Lo que hace diferente al tooling de Go
El go command no es solo un compilador. Es un statement sobre cómo debería funcionar el tooling de un lenguaje. Formato sin configuración. Tests sin framework externo. Compilación cruzada con dos variables de entorno. Gestión de dependencias sin archivo de lock separado (el go.sum se autogenera).
Hay cosas que no cubre: linting avanzado (necesitas golangci-lint), hot reload (necesitas air o similar), y gestión de releases (necesitas goreleaser o scripts). Pero la base que ofrece el toolchain estándar es más completa que la de cualquier otro lenguaje que haya usado.
Si estás empezando con Go, dedica una hora a explorar go help y los subcomandos que hemos visto. Esa hora te va a ahorrar días de configuración de herramientas que en otros ecosistemas das por sentado que necesitas. Si quieres una guía para dar los primeros pasos, empieza por cómo empezar con Go.


