Séparer les tests unitaires et les tests d'intégration dans Go

97

Existe-t-il une meilleure pratique établie pour séparer les tests unitaires et les tests d'intégration dans GoLang (témoigner)? J'ai un mélange de tests unitaires (qui ne reposent sur aucune ressource externe et donc fonctionnent très vite) et de tests d'intégration (qui reposent sur des ressources externes et donc s'exécutent plus lentement). Donc, je veux pouvoir contrôler s'il faut ou non inclure les tests d'intégration quand je dis go test.

La technique la plus simple semble être de définir un indicateur -integrate dans main:

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

Et puis pour ajouter une instruction if en haut de chaque test d'intégration:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

Est-ce le mieux que je puisse faire? J'ai cherché dans la documentation du témoignage pour voir s'il y avait peut-être une convention de dénomination ou quelque chose qui accomplit cela pour moi, mais je n'ai rien trouvé. Est-ce que je manque quelque chose?

Craig Jones
la source
2
Je pense que le stdlib utilise -short pour désactiver les tests qui frappent le réseau (et d'autres trucs de longue durée aussi). Sinon, votre solution semble correcte.
Volker du
-short est une bonne option, tout comme vos indicateurs de build personnalisés, mais vos indicateurs n'ont pas besoin d'être dans main. si vous définissez le var comme en var integration = flag.Bool("integration", true, "Enable integration testing.")dehors d'une fonction, la variable apparaîtra dans la portée du package et l'indicateur fonctionnera correctement
Atifm

Réponses:

155

@ Ainar-G suggère plusieurs grands modèles pour séparer les tests.

Cet ensemble de pratiques Go de SoundCloud recommande d'utiliser des balises de construction ( décrites dans la section «Contraintes de construction» du package de construction ) pour sélectionner les tests à exécuter:

Écrivez un integration_test.go et donnez-lui une balise de construction d'intégration. Définissez des indicateurs (globaux) pour des éléments tels que les adresses de service et les chaînes de connexion, et utilisez-les dans vos tests.

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

go test prend des balises de construction comme go build, vous pouvez donc appeler go test -tags=integration. Il synthétise également un package main qui appelle flag.Parse, donc tous les indicateurs déclarés et visibles seront traités et disponibles pour vos tests.

Comme option similaire, vous pouvez également exécuter des tests d'intégration par défaut en utilisant une condition de construction // +build !unit, puis les désactiver à la demande en exécutantgo test -tags=unit .

Commentaires de @adamc:

Pour toute personne essayant d'utiliser des balises de construction, il est important que le // +build testcommentaire soit la première ligne de votre fichier et que vous incluiez une ligne vide après le commentaire, sinon le-tags commande ignorera la directive.

De plus, la balise utilisée dans le commentaire de construction ne peut pas avoir de tiret, bien que les traits de soulignement soient autorisés. Par exemple, // +build unit-testsne fonctionnera pas, alors que // +build unit_testscela fonctionnera.

Alex
la source
1
J'utilise cela depuis un certain temps maintenant et c'est de loin l'approche la plus logique et la plus simple.
Ory Band
1
si vous avez des tests unitaires dans le même package, vous devez définir des // + build unittests unitaires et utiliser -tag unit pour exécuter les tests
LeoCBS
2
@ Tyler.z.yang pouvez-vous fournir un lien ou plus de détails sur la dépréciation des balises? Je n'ai pas trouvé de telles informations. J'utilise des balises avec go1.8 pour la manière décrite dans la réponse et aussi pour les types et les fonctions moqueurs dans les tests. C'est une bonne alternative aux interfaces je pense.
Alexander I.Grafov
2
Pour toute autre personne essayant d'utiliser des balises de construction, il est important que le // +buildcommentaire de test soit la première ligne de votre fichier et que vous incluiez une ligne vide après le commentaire, sinon la -tagscommande ignorera la directive. De plus, la balise utilisée dans le commentaire de construction ne peut pas avoir de tiret, bien que les traits de soulignement soient autorisés. Par exemple, // +build unit-testsne fonctionnera pas, alors que // +build unit_testscela fonctionnera
adamc
6
Comment gérer les jokers? go test -tags=integration ./...ne fonctionne pas, il ignore le tag
Erika Dsouza
53

Pour des détails sur mon commentaire à @ excellente réponse de Ainar-G, au cours de la dernière année , je l' ai utilisé la combinaison -shortavec Integrationconvention de nommage pour obtenir le meilleur des deux mondes.

Tests unitaires et d'intégration harmonie, dans le même fichier

Drapeaux Build me auparavant forcé d'avoir plusieurs fichiers ( services_test.go, services_integration_test.go, etc.).

Au lieu de cela, prenez cet exemple ci-dessous où les deux premiers sont des tests unitaires et j'ai un test d'intégration à la fin:

package services

import "testing"

func TestServiceFunc(t *testing.T) {
    t.Parallel()
    ...
}

func TestInvalidServiceFunc3(t *testing.T) {
    t.Parallel()
    ...
}

func TestPostgresVersionIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    ...
}

Notez que le dernier test a la convention de:

  1. en utilisant Integrationdans le nom du test.
  2. vérifier s'il fonctionne sous la -shortdirective flag.

Fondamentalement, la spécification dit: "écrivez tous les tests normalement. S'il s'agit de tests de longue durée ou d'un test d'intégration, suivez cette convention de dénomination et vérifiez que -shortvous êtes gentil avec vos pairs."

Exécuter uniquement des tests unitaires:

go test -v -short

cela vous fournit un bel ensemble de messages comme:

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

Exécuter les tests d'intégration uniquement:

go test -run Integration

Cela exécute uniquement les tests d'intégration. Utile pour tester la fumée des canaris en production.

De toute évidence, l'inconvénient de cette approche est que si quelqu'un s'exécute go test, sans l' -shortindicateur, il exécutera par défaut tous les tests - tests unitaires et d'intégration.

En réalité, si votre projet est suffisamment grand pour avoir des tests unitaires et d'intégration, vous utilisez probablement un Makefileoù vous pouvez avoir des directives simples à utiliser go test -short. Ou, mettez-le simplement dans votre README.mddossier et appelez-le le jour.

eduncan911
la source
3
aime la simplicité
Jacob Stanley
Créez-vous un package séparé pour un tel test afin d'accéder uniquement aux parties publiques du package? Ou tout mélangé?
Dr.eel
@ Dr.eel Eh bien, c'est OT de la réponse. Mais personnellement, je préfère les deux: un nom de package différent pour les tests afin que je puisse importmon package et le tester, ce qui finit par me montrer à quoi ressemble mon API pour les autres. Je fais ensuite le suivi de toute logique restante qui doit être couverte en tant que noms de packages de test internes.
eduncan911
@ eduncan911 Merci pour la réponse! Donc, si je comprends bien, il package servicescontient un test d'intégration, donc pour tester l'API et le package en tant que boîte noire, nous devrions le nommer d'une autre manière, package services_integration_testcela ne nous donnera pas une chance de travailler avec des structures internes. Ainsi, le package pour les tests unitaires (accès aux internes) doit être nommé package services. Est-ce vrai?
Dr.eel
C'est exact, oui. Voici un exemple clair de la façon dont je le fais: github.com/eduncan911/podcast (notez une couverture de code à 100%, en utilisant des exemples)
eduncan911
50

Je vois trois solutions possibles. La première consiste à utiliser le mode court pour les tests unitaires. Donc, vous utiliseriez go test -shortavec des tests unitaires et la même chose mais sans le-short drapeau pour exécuter vos tests d'intégration également. La bibliothèque standard utilise le mode court pour ignorer les tests de longue durée ou pour les accélérer en fournissant des données plus simples.

La seconde consiste à utiliser une convention et à appeler vos tests soit TestUnitFooou TestIntegrationFoo, puis à utiliser l' -runindicateur de test pour indiquer les tests à exécuter. Vous utiliseriez donc go test -run 'Unit'pour les tests unitaires etgo test -run 'Integration' pour les tests d'intégration.

La troisième option consiste à utiliser une variable d'environnement et à l'inclure dans la configuration de vos tests avec os.Getenv. Ensuite, vous utiliseriez simple go testpour les tests unitaires etFOO_TEST_INTEGRATION=true go test pour les tests d'intégration.

Personnellement, je préférerais la -shortsolution car elle est plus simple et utilisée dans la bibliothèque standard, il semble donc que ce soit un moyen de facto de séparer / simplifier les tests de longue durée. Mais les solutions -runet os.Getenvoffrent plus de flexibilité (plus de prudence est également requise, car les expressions rationnelles sont impliquées -run).

Ainar-G
la source
1
notez que les coureurs de test de la communauté (par exemple Tester-Go) communs aux IDE (Atom, Sublime, etc.) ont l'option intégrée pour s'exécuter avec -shortflag, avec -coverageet d'autres. par conséquent, j'utilise une combinaison des deux Intégration dans le nom du test, ainsi que des if testing.Short()vérifications dans ces tests. cela me permet d'avoir le meilleur des deux mondes: exécuter avec les -shortIDE et go test -run "Integration"
n'exécuter
5

J'essayais de trouver une solution pour la même chose récemment. Voici mes critères:

  • La solution doit être universelle
  • Pas de package séparé pour les tests d'intégration
  • La séparation doit être terminée (je devrais pouvoir exécuter des tests d'intégration uniquement )
  • Aucune convention de dénomination spéciale pour les tests d'intégration
  • Cela devrait bien fonctionner sans outillage supplémentaire

Les solutions susmentionnées (drapeau personnalisé, balise de construction personnalisée, variables d'environnement) ne répondaient pas vraiment à tous les critères ci-dessus, donc après un peu de fouille et de lecture, j'ai trouvé cette solution:

package main

import (
    "flag"
    "regexp"
    "testing"
)

func TestIntegration(t *testing.T) {
    if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
        t.Skip("skipping as execution was not requested explicitly using go test -run")
    }

    t.Parallel()

    t.Run("HelloWorld", testHelloWorld)
    t.Run("SayHello", testSayHello)
}

La mise en œuvre est simple et minimale. Bien que cela nécessite une convention simple pour les tests, il est moins sujet aux erreurs. Une amélioration supplémentaire pourrait être l'exportation du code vers une fonction d'assistance.

Usage

Exécutez les tests d'intégration uniquement sur tous les packages d'un projet:

go test -v ./... -run ^TestIntegration$

Exécutez tous les tests ( réguliers et d'intégration):

go test -v ./... -run .\*

Exécutez uniquement des tests réguliers :

go test -v ./...

Cette solution fonctionne bien sans outillage, mais un Makefile ou certains alias peuvent la rendre plus facile à utiliser. Il peut également être facilement intégré dans n'importe quel IDE prenant en charge l'exécution de tests go.

L'exemple complet peut être trouvé ici: https://github.com/sagikazarmark/modern-go-application

mark.sagikazar
la source