¿Cómo puedo realizar la configuración de prueba usando el paquete de prueba en Go?

111

¿Cómo puedo realizar el procesamiento general de la configuración de la prueba que prepara el escenario para todas las pruebas cuando se utiliza el paquete de pruebas ?

Como ejemplo en Nunit hay un [SetUp]atributo.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}
miltonb
fuente
2
A partir de 1.4, puede tener configuración y desmontaje global
Salvador Dali

Respuestas:

159

A partir de Go 1.4, puede implementar la configuración / desmontaje (no es necesario copiar sus funciones antes / después de cada prueba). La documentación se describe aquí en la sección principal :

TestMain se ejecuta en la goroutine principal y puede realizar cualquier configuración y desmontaje que sea necesario en torno a una llamada a m.Run. Luego debería llamar a os.Salir con el resultado de m.Run

Me tomó algún tiempo darme cuenta de que esto significa que si una prueba contiene una función func TestMain(m *testing.M), esta función se llamará en lugar de ejecutar la prueba. Y en esta función puedo definir cómo se ejecutarán las pruebas. Por ejemplo, puedo implementar la configuración y el desmontaje global:

func TestMain(m *testing.M) {
    setup()
    code := m.Run() 
    shutdown()
    os.Exit(code)
}

Puede encontrar un par de otros ejemplos aquí .

La función TestMain agregada al marco de prueba de Go en la última versión es una solución simple para varios casos de uso de prueba. TestMain proporciona un enlace global para realizar la configuración y el apagado, controlar el entorno de prueba, ejecutar código diferente en un proceso hijo o verificar los recursos filtrados por el código de prueba. La mayoría de los paquetes no necesitarán un TestMain, pero es una adición bienvenida para aquellos momentos en que se necesita.

Salvador Dalí
fuente
17
TestMainestá una vez en un paquete, por lo que no es tan útil. Encuentro que las subpruebas son mejores para propósitos más complejos.
Inanc Gumus
3
¿Cómo se supone que debe pasar el contexto de la función de configuración a las pruebas sin usar variables globales? Por ejemplo, si mySetupFunction () crea un directorio temporal para realizar pruebas (con un nombre aleatorio único), ¿cómo saben las pruebas el nombre del directorio? ¿Debe haber un lugar para establecer este contexto?
Lqueryvg
1
Parece que esta es la forma oficial de manejar los ganchos de antes y después para las pruebas, consulte golang.org/pkg/testing/#hdr-Main para obtener la documentación oficial
de-jcup
4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030
1
tenga en cuenta que 'código: = m.Run ()' es el que ejecuta otras funciones de prueba.
Alex Punnen
49

Esto se puede lograr poniendo una init()función en el _test.goarchivo. Esto se ejecutará antes de la init()función.

// package_test.go
package main

func init() {
     /* load test data */
}

Se llamará a _test.init () antes de la función del paquete init ().

miltonb
fuente
2
Sé que está respondiendo a su propia pregunta, por lo que probablemente esto satisfaga su propio caso de uso, pero esto no es equivalente al ejemplo de NUnit que ha incluido en su pregunta.
James Henstridge
Bueno, @james, he mostrado un pensamiento sobre la forma de responder al problema y otros ya han proporcionado algunas buenas ideas, incluida la suya. Es útil obtener influencias externas para afinar el enfoque. Gracias.
miltonb
2
Lo suficientemente justo. Lo que ha mostrado en esta respuesta está algo más cerca de usar el [TestFixtureSetUp]atributo de NUnit en su lugar.
James Henstridge
2
no incluye parte de desmontaje
Taras Matsyk
7
Esta no es una buena solución si su archivo de prueba está en el mismo paquete con la función principal.
MouseWanted
28

Dada una función simple para prueba unitaria:

package math

func Sum(a, b int) int {
    return a + b
}

Puede probarlo con una función de configuración que devuelve la función de desmontaje. Y después de llamar a setup (), puede realizar una llamada diferida a teardown ().

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
    t.Log("setup test case")
    return func(t *testing.T) {
        t.Log("teardown test case")
    }
}

func setupSubTest(t *testing.T) func(t *testing.T) {
    t.Log("setup sub test")
    return func(t *testing.T) {
        t.Log("teardown sub test")
    }
}

func TestAddition(t *testing.T) {
    cases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"add", 2, 2, 4},
        {"minus", 0, -2, -2},
        {"zero", 0, 0, 0},
    }

    teardownTestCase := setupTestCase(t)
    defer teardownTestCase(t)

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            teardownSubTest := setupSubTest(t)
            defer teardownSubTest(t)

            result := Sum(tc.a, tc.b)
            if result != tc.expected {
                t.Fatalf("expected sum %v, but got %v", tc.expected, result)
            }
        })
    }
}

La herramienta de prueba Go informará las declaraciones de registro en la consola de shell:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
    math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    math_test.go:8: teardown test case
PASS
ok      github.com/kare/go-unit-test-setup-teardown 0.010s
% 

Puede pasar algunos parámetros adicionales para configurar / desmontar con este enfoque.

Kare Nuorteva
fuente
2
Ahora que es un truco realmente simple pero efectivo. Gran uso de la sintaxis de Go.
miltonb
1
Sí, pero aumenta el anidamiento (una especie de pirámide de fatalidad en javascript ). Y las pruebas no se ejecutan automáticamente por la suite como en las pruebas externas.
Inanc Gumus
12

Normalmente, las pruebas en go no están escritas con el mismo estilo que otros idiomas. A menudo, hay relativamente menos funciones de prueba, pero cada una contiene un conjunto de casos de prueba basado en tablas. Vea este artículo escrito por un miembro del equipo de Go.

Con una prueba basada en tablas, simplemente coloque cualquier código de configuración antes del ciclo que ejecuta los casos de prueba individuales especificados en la tabla, y luego coloque cualquier código de limpieza.

Si todavía tiene un código de configuración compartido entre las funciones de prueba, puede extraer el código de configuración compartido en una función y usar un sync.Oncesi es importante que se ejecute exactamente una vez (o como sugiere otra respuesta, use init(), pero esto tiene la desventaja de que la configuración se realizará incluso si los casos de prueba no se ejecutan (quizás porque ha limitado los casos de prueba mediante el uso go test -run <regexp>).

Yo diría que si cree que necesita una configuración compartida entre diferentes pruebas que se ejecutan exactamente una vez, debería pensar si realmente la necesita, y si una prueba basada en tablas no sería mejor.

Paul Hankin
fuente
6
Eso es genial cuando se prueban cosas triviales como un analizador de indicadores o un algoritmo que agita números. Pero realmente no ayuda cuando se intenta probar diversas funciones que requieren un código repetitivo similar. Supongo que podría definir mis funciones de prueba en una matriz e iterar sobre ellas, pero en realidad no se basa tanto en tablas como en un bucle simple que realmente debería integrarse en el marco de prueba en sí (en la forma de un conjunto de pruebas adecuado con funciones de configuración / desmontaje)
iamtheddrman
9

El marco de pruebas de Go no tiene nada equivalente al atributo SetUp de NUnit (marcando una función que se llamará antes de cada prueba en la suite). Sin embargo, hay algunas opciones:

  1. Simplemente llame a su SetUpfunción desde cada prueba donde sea necesario.

  2. Utilice una extensión del marco de pruebas de Go que implementa paradigmas y conceptos de xUnit. Me vienen a la mente tres opciones fuertes:

Cada una de estas bibliotecas lo alienta a organizar sus pruebas en suites / accesorios similares a otros marcos de xUnit, y llamará a los métodos de configuración en el tipo de suite / accesorio antes de cada uno de los Test*métodos.

James Henstridge
fuente
0

Enchufe descarado, creé https://github.com/houqp/gtest para ayudar a resolver exactamente este problema.

Aquí hay un ejemplo rápido:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

Puede crear cualquier grupo de prueba que desee dentro de un paquete con cada uno de ellos utilizando un conjunto diferente de rutinas de configuración / desmontaje.

houqp
fuente