Esta pagina se ve mejor con JavaScript habilitado

Go unit tests

 ·  🎃 kr0m

Tan importante como programar nuevas funcionalidades es asegurarse de que no se rompe nada en cada cambio, afortunadamente Go lleva incluído 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 y lo generará Go por nosotros:

mkdir go_testing
cd go_testing/
go mod init name

Podemos ver el contenido del fichero go.mod:

cat go.mod

module name

go 1.20

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]

vi name.go

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)
    }
}

Creamos el test unitario que comprobará que llamando a la función Hello() esta devuelva el valor correcto:

vi name_test.go

package main

import (
    "testing"
    "regexp"
)

// Test Hello() function with input string:
func TestHelloName(t *testing.T) {
    name := "Kr0m" // Function input
    want := regexp.MustCompile(`\b`+name+`\b`) // What we expect
    msg, err := Hello(name) // Save Hello() returned values
    // Compare want with msg OR an error has ocurred:
    if !want.MatchString(msg) || err != nil {
        t.Fatalf(`Hello(name) = %q, %v, want match for %#q, nil`, msg, err, want)
    }
}

// Test Hello() function with empty string:
func TestHelloEmpty(t *testing.T) {
    msg, err := Hello("") // Save Hello() returned values
    // If msg is not empty string OR err NULL:
    if msg != "" || err == nil {
        t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
    }
}

Pasamos los tests sin problemas:

go test

PASS
ok  	name	0.002s

Introducimos un bug, haciendo que la función siempre retorne [AAAA,nil]:

vi name.go

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:

go test

Podemos ver que esta vez ha fallado:

--- FAIL: TestHelloName (0.00s)
    name_test.go:15: Hello(name) = "AAAA", <nil>, want match for `\bKr0m\b`, nil
FAIL
exit status 1
FAIL	name	0.002s

La línea 15 de name_test:

if !want.MatchString(msg) || err != nil {
    t.Fatalf(`Hello(name) = %q, %v, want match for %#q, nil`, msg, err, want)
}

Efectivamente want no machea msg:

name := "Kr0m"
want := regexp.MustCompile(`\b`+name+`\b`)
// msg content: AAAA
Si te ha gustado el artículo puedes invitarme a un RedBull aquí