Tan importante como programar nuevas funcionalidades es asegurarse de que no se rompe nada en cada cambio, afortunadamente Go incluye todo lo necesario para ejecutar tests de forma rápida y cómoda.
Si eres nuevo en el mundo de Go te recomiendo los siguientes artÃculos anteriores:
Para conseguir una construcción reproducible Go recomienda utilizar versiones similares de Go, para ello podemos generar el fichero go.mod de forma manual o inicializar un módulo para que lo genere Go por nosotros:
cd go_testing/
go mod init name
Podemos ver el contenido del fichero go.mod:
module name
go 1.21.12
Construcción Reproducible:
La inclusión de un archivo go.mod en tu proyecto puede contribuir a la construcción reproducible. Esto significa que, al compartir tu código con
otros desarrolladores o al ejecutar pruebas en diferentes entornos, las mismas versiones de las dependencias se utilizarán, lo que puede ayudar
a evitar problemas de compatibilidad.
A modo de ejemplo simplemente tendremos una función llamada Hello que si le llega un string de entrada, devolverá [string,nil]
, si se le pasa una entrada vacÃa devolverá ["",ERROR]
package main
import (
"errors"
"fmt"
)
func hello(name string) (string, error) {
if name == "" {
return name, errors.New("Empty name")
}
return name, nil
}
func main() {
name, error := hello("Kr0m")
if error != nil {
fmt.Println("ERROR:", error)
} else {
fmt.Println("Name:", name)
}
}
Todos los ficheros de tests deben seguir el patrón XXX_test.go, se debe importar el package testing y las funciones deben empezar por Test.
Creamos el test unitario el cual comprobará que la función hello() devuelva un valor correcto,
package main
import (
"testing"
)
// Test hello() function with input string:
func TestHelloName(t *testing.T) {
// Function input
name := "Kr0m"
// What we expect
want := "Kr0m"
// Save hello() returned values
msg, err := hello(name)
// Compare want with msg OR an error has ocurred:
if msg != want || err != nil {
t.Fatalf(`hello(name) = %v, %v, want match for %v, nil`, msg, err, want)
}
}
// Test hello() function with empty string:
func TestHelloEmpty(t *testing.T) {
// Save hello() returned values
msg, err := hello("")
// If msg is not empty string OR err NULL:
if msg != "" || err == nil {
t.Fatalf(`hello("") = %v, %v, want "", error`, msg, err)
}
}
Pasamos los tests sin problemas:
PASS
ok name 0.002s
Si añadimos el parámetro -cover
podremos ver el porcentage de código que nuestros tests cubren:
PASS
coverage: 42.9% of statements
ok name 0.002s
Introducimos un bug, haciendo que la función siempre retorne [AAAA,nil]
:
package main
import (
"errors"
"fmt"
)
func Hello(name string) (string, error) {
if name == "" {
return name, errors.New("Empty name")
}
name = "AAAA" // BUG
return name, nil
}
func main() {
name, error := Hello("Kr0m")
if error != nil {
fmt.Println("ERROR:", error)
} else {
fmt.Println("Name:", name)
}
}
Volvemos a pasar los tests:
Podemos ver que esta vez ha fallado:
--- FAIL: TestHelloName (0.00s)
name_test.go:17: hello(name) = AAAA, <nil>, want match for Kr0m, nil
FAIL
exit status 1
FAIL name 0.002s
La lÃnea 17 de name_test:
if msg != want || err != nil {
t.Fatalf(`hello(name) = %v, %v, want match for %v, nil`, msg, err, want)
}
Efectivamente want
no machea msg
:
name := "Kr0m"
want := "Kr0m"
// msg -> AAAA