Volver a la página principal
martes 16 julio 2024
23

El Uso de Pruebas Unitarias en Go

¿Qué son las Pruebas Unitarias?

Las pruebas unitarias son pruebas automatizadas que validan el comportamiento de pequeñas unidades de código, como funciones o métodos. El objetivo es asegurar que cada unidad funcione correctamente en aislamiento, detectando errores de forma temprana en el ciclo de desarrollo.

Configuración del Entorno de Pruebas

Go incluye herramientas integradas para escribir y ejecutar pruebas. Para comenzar, se debe crear un archivo de pruebas con el sufijo _test.go. Este archivo contiene funciones de prueba que utilizan el paquete testing.

Estructura Básica de una Prueba

A continuación, se muestra un ejemplo básico de una prueba unitaria en Go:

// archivo: math_test.go

package main

import (
    "testing"
)

// Función a probar
func Sum(a, b int) int {
    return a + b
}

// Función de prueba
func TestSum(t *testing.T) {
    result := Sum(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Sum(2, 3) = %d; expected %d", result, expected)
    }
}

En este ejemplo, Sum es la función a probar y TestSum es la función de prueba. La función de prueba compara el resultado de Sum con el valor esperado y usa t.Errorf para reportar un error si los valores no coinciden.

Ejecutar Pruebas

Para ejecutar las pruebas, utiliza el comando go test en el directorio que contiene los archivos de prueba:

$ go test

Este comando buscará automáticamente todos los archivos _test.go y ejecutará las funciones de prueba que comienzan con Test.

Pruebas con Tablas de Prueba

Las tablas de prueba son una técnica común en Go para organizar múltiples casos de prueba en una sola función de prueba. Esto permite probar varios escenarios de manera concisa y clara.

Ejemplo de Tabla de Prueba

// archivo: math_test.go

package main

import (
    "testing"
)

// Función a probar
func Multiply(a, b int) int {
    return a * b
}

// Función de prueba con tabla de prueba
func TestMultiply(t *testing.T) {
    var tests = []struct {
        a, b, expected int
    }{
        {2, 3, 6},
        {4, 5, 20},
        {0, 1, 0},
        {7, 8, 56},
    }

    for _, tt := range tests {
        result := Multiply(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Multiply(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)
        }
    }
}

En este ejemplo, la función TestMultiply define una tabla de prueba con varios casos. Luego, recorre la tabla y ejecuta la función Multiply para cada caso, verificando si el resultado coincide con el valor esperado.

Pruebas de Funciones que Retornan Errores

Es común que las funciones en Go retornen errores. Las pruebas unitarias deben verificar tanto los resultados como los errores.

Ejemplo de Prueba de Función con Error

// archivo: divide_test.go

package main

import (
    "errors"
    "testing"
)

// Función a probar
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Función de prueba para manejo de errores
func TestDivide(t *testing.T) {
    result, err := Divide(4, 2)
    if err != nil {
        t.Errorf("unexpected error: %v", err)
    }
    expected := 2.0
    if result != expected {
        t.Errorf("Divide(4, 2) = %f; expected %f", result, expected)
    }

    _, err = Divide(4, 0)
    if err == nil {
        t.Error("expected an error but got nil")
    }
}

En este ejemplo, TestDivide prueba la función Divide, verificando tanto el resultado de la división como el manejo del error cuando el divisor es cero.

Pruebas de Métodos y Uso de Structs

Las pruebas unitarias también pueden probar métodos de estructuras. Es útil para probar el comportamiento de métodos asociados a tipos definidos por el usuario.

Ejemplo de Prueba de Métodos

// archivo: rectangle_test.go

package main

import (
    "testing"
)

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func TestRectangleArea(t *testing.T) {
    rect := Rectangle{Width: 3, Height: 4}
    result := rect.Area()
    expected := 12.0
    if result != expected {
        t.Errorf("Rectangle.Area() = %f; expected %f", result, expected)
    }
}

En este ejemplo, TestRectangleArea prueba el método Area de la estructura Rectangle.

Uso de t.Run para Subpruebas

Go permite definir subpruebas dentro de una función de prueba utilizando t.Run. Esto es útil para organizar mejor las pruebas y proporcionar salidas de prueba más claras.

Ejemplo de Subpruebas

// archivo: math_test.go

package main

import (
    "testing"
)

func TestOperations(t *testing.T) {
    t.Run("Sum", func(t *testing.T) {
        result := Sum(2, 3)
        expected := 5
        if result != expected {
            t.Errorf("Sum(2, 3) = %d; expected %d", result, expected)
        }
    })

    t.Run("Multiply", func(t *testing.T) {
        result := Multiply(2, 3)
        expected := 6
        if result != expected {
            t.Errorf("Multiply(2, 3) = %d; expected %d", result, expected)
        }
    })
}

En este ejemplo, TestOperations define subpruebas para Sum y Multiply, proporcionando una mejor organización y claridad en las salidas de prueba.

Pruebas de Código Concurrente

Go tiene soporte nativo para concurrencia. Es crucial probar correctamente el código concurrente para asegurarse de que funcione correctamente en situaciones multi-hilo.

Ejemplo de Prueba de Código Concurrente

// archivo: concurrent_test.go

package main

import (
    "sync"
    "testing"
)

func TestConcurrentIncrement(t *testing.T) {
    var counter int
    var mu sync.Mutex
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            defer mu.Unlock()
            counter++
        }()
    }

    wg.Wait()

    if counter != 1000 {
        t.Errorf("counter = %d; expected 1000", counter)
    }
}

En este ejemplo, TestConcurrentIncrement prueba una operación de incremento concurrente utilizando un mutex para asegurar que las operaciones sean seguras en concurrencia.

Cobertura de Código

La herramienta go test también puede medir la cobertura de código, indicando qué partes del código son ejecutadas por las pruebas.

Ejecutar Pruebas con Cobertura

$ go test -cover

Para generar un informe de cobertura más detallado:

$ go test -coverprofile=coverage.out
$ go tool cover -html=coverage.out

Este comando genera un archivo de informe de cobertura y lo presenta en un formato HTML.

Conclusión

Las pruebas unitarias en Go son esenciales para asegurar la calidad y fiabilidad del código. Gracias a su soporte integrado para pruebas y su enfoque en la simplicidad, Go facilita la escritura y ejecución de pruebas unitarias. Desde pruebas básicas hasta técnicas avanzadas como tablas de prueba y subpruebas, Go proporciona las herramientas necesarias para mantener un alto estándar de calidad en el desarrollo de software.

Compartir:
Creado por:
Author photo

Jorge García

Fullstack developer