Go by Example: Testing and Benchmarking

Las pruebas unitarias son una parte importante de la escritura de programas en Go con principios. El paquete testing proporciona las herramientas que necesitamos para escribir pruebas unitarias y el comando go test ejecuta las pruebas.

A modo de demostración, este código está en el paquete main, pero podría ser cualquier paquete. El código de prueba típicamente vive en el mismo paquete que el código que prueba.

package main
import (
    "fmt"
    "testing"
)

Vamos a probar esta simple implementación de un mínimo entero. Típicamente, el código que estamos probando estaría en un archivo fuente llamado algo así como intutils.go, y el archivo de prueba para él se llamaría entonces intutils_test.go.

func IntMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

Una prueba se crea escribiendo una función con un nombre que comienza con Test.

func TestIntMinBasic(t *testing.T) {
    ans := IntMin(2, -2)
    if ans != -2 {

t.Error* informará de fallos en las pruebas pero continuará ejecutando la prueba. t.Fatal* informará de fallos en las pruebas y detendrá la prueba inmediatamente.

        t.Errorf("IntMin(2, -2) = %d; want -2", ans)
    }
}

Escribir pruebas puede ser repetitivo, por lo que es idiomático usar un estilo basado en tablas, donde las entradas de prueba y los resultados esperados se enumeran en una tabla y un solo bucle las recorre y realiza la lógica de la prueba.

func TestIntMinTableDriven(t *testing.T) {
    var tests = []struct {
        a, b int
        want int
    }{
        {0, 1, 0},
        {1, 0, 0},
        {2, -2, -2},
        {0, -1, -1},
        {-1, 0, -1},
    }

t.Run permite ejecutar “subpruebas”, una para cada entrada de la tabla. Estas se muestran por separado al ejecutar go test -v.

    for _, tt := range tests {
        testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
        t.Run(testname, func(t *testing.T) {
            ans := IntMin(tt.a, tt.b)
            if ans != tt.want {
                t.Errorf("got %d, want %d", ans, tt.want)
            }
        })
    }
}

Las pruebas de rendimiento típicamente van en archivos _test.go y están nombradas comenzando con Benchmark. El ejecutor de testing ejecuta cada función de rendimiento varias veces, aumentando b.N en cada ejecución hasta que recopila una medición precisa.

func BenchmarkIntMin(b *testing.B) {

Normalmente, la prueba de rendimiento ejecuta una función que estamos evaluando en un bucle b.N veces.

    for i := 0; i < b.N; i++ {
        IntMin(1, 2)
    }
}

Ejecuta todas las pruebas en el proyecto actual en modo detallado.

$ go test -v
== RUN   TestIntMinBasic
--- PASS: TestIntMinBasic (0.00s)
=== RUN   TestIntMinTableDriven
=== RUN   TestIntMinTableDriven/0,1
=== RUN   TestIntMinTableDriven/1,0
=== RUN   TestIntMinTableDriven/2,-2
=== RUN   TestIntMinTableDriven/0,-1
=== RUN   TestIntMinTableDriven/-1,0
--- PASS: TestIntMinTableDriven (0.00s)
    --- PASS: TestIntMinTableDriven/0,1 (0.00s)
    --- PASS: TestIntMinTableDriven/1,0 (0.00s)
    --- PASS: TestIntMinTableDriven/2,-2 (0.00s)
    --- PASS: TestIntMinTableDriven/0,-1 (0.00s)
    --- PASS: TestIntMinTableDriven/-1,0 (0.00s)
PASS
ok      examples/testing-and-benchmarking    0.023s

Ejecuta todas las pruebas de rendimiento en el proyecto actual. Todas las pruebas se ejecutan antes de las pruebas de rendimiento. La bandera bench filtra los nombres de las funciones de rendimiento con una expresión regular.

$ go test -bench=.
goos: darwin
goarch: arm64
pkg: examples/testing
BenchmarkIntMin-8 1000000000 0.3136 ns/op
PASS
ok      examples/testing-and-benchmarking    0.351s

Siguiente ejemplo: Command-Line Arguments.