¿Cómo ejecutar todas las pruebas en un proyecto Go y qué sucede "bajo el capó"?
\nCada desarrollador de Go al menos una vez en la vida ha escrito el comando mágico go test ./... en la terminal y ha visto cómo las marcas verdes corren por la pantalla. Pero, ¿alguna vez te has preguntado qué sucede realmente en ese momento? ¿Por qué las pruebas se ejecutan exactamente así y no de otra manera? ¿Y por qué a veces go test omite silenciosamente tus nuevas pruebas?
En este artículo no solo responderemos a la pregunta popular de Stack Overflow (donde ha acumulado miles de visitas), sino que también miraremos bajo el capó del compilador de Go y descubriremos cómo está diseñado el mecanismo de pruebas. ¡Allá vamos!
\n\n¿Por qué go test ./... es magia?
\nComencemos con lo básico. El comando go test sin argumentos ejecuta pruebas solo en el paquete actual. Pero si tienes un proyecto con docenas de paquetes (y en producción real es así), revisarlos manualmente es como una tortura medieval. Aquí es donde ./... viene al rescate.
Este patrón significa: "encuentra todos los paquetes recursivamente, comenzando desde el directorio actual". Go recorre el árbol de directorios, ignorando vendor y los módulos, y recopila una lista de todos los paquetes. Para cada uno de ellos, ejecuta una instancia separada de go test.
Pero, ¿qué pasa si quieres ejecutar pruebas solo en ciertos directorios? Por ejemplo, solo en ./internal/...? Funciona igual: go test ./internal/....
Importante: si usas Go Modules (y desde Go 1.16+ es estándar), entonces ./... solo funciona dentro del módulo actual. Para probar todos los módulos en un workspace, tendrás que usar go test ./... en cada uno de ellos o configurar go.work.
¿Y qué es un "paquete" en el contexto de go test?
\nGo opera con el concepto de paquete: un grupo de archivos .go en un mismo directorio. Los archivos de prueba (con el sufijo _test.go) también se consideran parte del paquete, pero se compilan por separado. Cuando ejecutas go test, Go compila un binario de prueba que incluye:
- \n
- El código fuente del paquete (pero solo lo que se usa en las pruebas) \n
- El código de las propias pruebas \n
- Un envoltorio especial de tiempo de ejecución para la ejecución y el informe \n
Este binario se ejecuta, realiza las pruebas y muestra el resultado. Todo el proceso es independiente para cada paquete.
\n\n¿Cómo decide Go qué pruebas ejecutar?
\nCuando escribes go test ./..., Go realiza los siguientes pasos:
- \n
- Recorrido del sistema de archivos — Go encuentra todos los directorios que contienen archivos
.go. \n - Filtrado — se ignoran los directorios
vendor,testdatay las carpetas ocultas (que comienzan con.). \n - Compilación — para cada paquete se crea un binario temporal. \n
- Ejecución — el binario se ejecuta y se recopilan los resultados. \n
- Almacenamiento en caché — si el código no ha cambiado, Go usa la caché y muestra
(cached). \n
El último punto es una causa frecuente de sorpresa. Modificaste la prueba, ejecutas go test ./..., y dice "ok (cached)". ¿Por qué? Porque Go almacena en caché los resultados basándose en un hash de los archivos de entrada. Si solo cambiaste un comentario o una importación no utilizada, el hash no cambia y la prueba no se vuelve a ejecutar.
Truco: puedes forzar la limpieza de la caché con el comando go clean -testcache. O usa la bandera -count=1, que obliga a Go a ignorar la caché: go test -count=1 ./....
Paralelismo: ¿por qué las pruebas no siempre son más rápidas?
\nPor defecto, go test ejecuta las pruebas dentro de un mismo paquete de forma secuencial. ¡Pero entre paquetes, en paralelo! La cantidad de paquetes simultáneos se determina con la bandera -p (por defecto, igual al número de CPUs).
Suena genial, pero hay un detalle: e