Comment puis-je effectuer une configuration de test à l'aide du package de test dans Go

111

Comment puis-je effectuer un traitement global de configuration de test qui prépare le terrain pour tous les tests lors de l'utilisation du package de test ?

A titre d'exemple dans Nunit, il y a un [SetUp]attribut.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}
Miltonb
la source
2
À partir de la version 1.4, vous pouvez avoir une configuration globale et un démontage
Salvador Dali

Réponses:

159

À partir de Go 1.4, vous pouvez implémenter setup / démontage (pas besoin de copier vos fonctions avant / après chaque test). La documentation est présentée ici dans la section principale :

TestMain s'exécute dans le goroutine principal et peut effectuer toutes les configurations et démontages nécessaires autour d'un appel à m.Run. Il devrait alors appeler os.Exit avec le résultat de m.Run

Il m'a fallu un certain temps pour comprendre que cela signifie que si un test contient une fonction, func TestMain(m *testing.M)cette fonction sera appelée au lieu d'exécuter le test. Et dans cette fonction, je peux définir comment les tests se dérouleront. Par exemple, je peux implémenter la configuration globale et le démontage:

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

Quelques autres exemples peuvent être trouvés ici .

La fonctionnalité TestMain ajoutée au cadre de test de Go dans la dernière version est une solution simple pour plusieurs cas d'utilisation de test. TestMain fournit un hook global pour effectuer l'installation et l'arrêt, contrôler l'environnement de test, exécuter un code différent dans un processus enfant ou vérifier les ressources divulguées par le code de test. La plupart des packages n'auront pas besoin d'un TestMain, mais c'est un ajout bienvenu pour les moments où cela est nécessaire.

Salvador Dali
la source
17
TestMainest une fois dans un paquet, donc ce n'est pas si utile. Je trouve que les sous - tests sont meilleurs pour des objectifs plus complexes.
Inanc Gumus
3
Comment êtes-vous censé passer le contexte de la fonction de configuration aux tests sans utiliser de variables globales? Par exemple, si mySetupFunction () crée un répertoire temporaire pour effectuer des tests (avec un nom unique et aléatoire), comment les tests connaissent-ils le nom du répertoire? Il doit y avoir un endroit pour définir ce contexte ??
Lqueryvg
1
Il semble que ce soit un moyen officiel de gérer les crochets avant et après pour les tests, voir golang.org/pkg/testing/#hdr-Main pour la documentation officielle
de-jcup
4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030
1
veuillez noter que 'code: = m.Run ()' est celui qui exécute d'autres TestFunctions!
Alex Punnen
49

Ceci peut être réalisé en mettant une init()fonction dans le _test.gofichier. Cela sera exécuté avant la init()fonction.

// package_test.go
package main

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

Le _test.init () sera appelé avant la fonction init () du package.

Miltonb
la source
2
Je sais que vous répondez à votre propre question, donc cela satisfait probablement votre propre cas d'utilisation, mais ce n'est pas équivalent à l'exemple NUnit que vous avez inclus dans votre question.
James Henstridge
Eh bien @james, j'ai montré une réflexion sur la manière de répondre au problème et d'autres ont déjà fourni de bonnes idées, y compris la vôtre. Il est utile d'obtenir des influences extérieures pour régler son approche. Merci.
miltonb
2
C'est suffisant. Ce que vous avez montré dans cette réponse est un peu plus proche de l'utilisation de l' [TestFixtureSetUp]attribut de NUnit à la place.
James Henstridge
2
il n'inclut pas la partie
démontable
7
Ce n'est pas une bonne solution si votre fichier de test est dans le même package avec la fonction main.
MouseWanted
28

Étant donné une fonction simple de test unitaire:

package math

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

Vous pouvez le tester avec une fonction de configuration qui renvoie la fonction de démontage. Et après avoir appelé setup (), vous pouvez faire un appel différé à 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)
            }
        })
    }
}

L'outil de test Go rapportera les instructions de journalisation dans la console 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
% 

Vous pouvez transmettre des paramètres supplémentaires à la configuration / au démontage avec cette approche.

Kare Nuorteva
la source
2
Voilà une véritable astuce simple mais efficace. Grande utilisation de la syntaxe Go.
miltonb
1
Ouais, mais ça augmente l'imbrication ( sorte de pyramide de malheur en javascript ). Et les tests ne sont pas exécutés automatiquement par la suite comme dans les tests externes.
Inanc Gumus
12

En règle générale, les tests de go ne sont pas écrits dans le même style que les autres langages. Souvent, il y a relativement moins de fonctions de test, mais chacune contient un ensemble de cas de test basé sur une table. Consultez cet article rédigé par l'un des membres de l'équipe Go.

Avec un test basé sur une table, vous placez simplement n'importe quel code de configuration avant la boucle qui exécute les cas de test individuels spécifiés dans le tableau, et placez tout code de nettoyage par la suite.

Si vous avez toujours un code de configuration partagé entre les fonctions de test, vous pouvez extraire le code de configuration partagé dans une fonction et utiliser a sync.Onces'il est important qu'il soit exécuté exactement une fois (ou comme une autre réponse le suggère, utilisez init(), mais cela présente l'inconvénient que la configuration sera fait même si les cas de test ne sont pas exécutés (peut-être parce que vous avez limité les cas de test en utilisant go test -run <regexp>.)

Je dirais que si vous pensez que vous avez besoin d'une configuration partagée entre différents tests qui est exécutée exactement une fois, vous devriez réfléchir si vous en avez vraiment besoin, et si un test basé sur une table ne serait pas meilleur.

Paul Hankin
la source
6
C'est génial lorsque vous testez des choses triviales comme un analyseur de drapeau ou un algorithme qui génère des nombres. Mais cela n'aide pas vraiment lorsque vous essayez de tester diverses fonctionnalités qui nécessitent toutes un code standard similaire. Je suppose que je pourrais définir mes fonctions de test dans un tableau et les itérer, mais ce n'est pas vraiment basé sur une table, mais plutôt une simple boucle qui devrait vraiment être intégrée au cadre de test lui-même (sous la forme d'une suite de tests appropriée avec fonctions de configuration / démontage)
iamtheddrman
9

Le framework de test Go n'a rien d'équivalent à l' attribut SetUp de NUnit (marquant une fonction à appeler avant chaque test de la suite). Il existe cependant quelques options:

  1. Appelez simplement votre SetUpfonction à partir de chaque test là où elle est nécessaire.

  2. Utilisez une extension du cadre de test de Go qui implémente les paradigmes et concepts xUnit. Trois options fortes me viennent à l'esprit:

Chacune de ces bibliothèques vous encourage à organiser vos tests en suites / fixtures similaires aux autres frameworks xUnit, et appellera les méthodes de configuration sur le type suite / fixture avant chacune des Test*méthodes.

James Henstridge
la source
0

Plug sans vergogne, j'ai créé https://github.com/houqp/gtest pour aider à résoudre exactement ce problème.

Voici un exemple rapide:

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

Vous pouvez créer comme n'importe quel groupe de test que vous voulez dans un package avec chacun d'eux en utilisant un ensemble différent de routines de configuration / démontage.

houqp
la source