web-dev-qa-db-fra.com

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

Existe-t-il une meilleure pratique établie pour la séparation des tests unitaires et des tests d'intégration dans GoLang (témoigner)? J'ai une combinaison de tests unitaires (qui ne dépendent d'aucune ressource externe et donc d'une exécution très rapide) et de tests d'intégration (qui reposent sur une ressource externe et s'exécutent donc plus lentement). Je souhaite donc pouvoir contrôler l’inclusion ou non des tests d’intégration lorsque je dis go test.

La technique la plus simple semble être de définir un drapeau -intégrate dans main: 

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

Et ensuite, 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 consulté la documentation de témoignage pour voir s'il existe peut-être une convention de dénomination ou quelque chose qui l'accomplit pour moi, mais je n'ai rien trouvé. Est-ce que je manque quelque chose?

61
Craig Jones

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

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

Écrivez un fichier integration_test.go et attribuez-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 les balises de construction comme go build, vous pouvez donc appeler go test -tags=integration. Il synthétise également un paquetage principal qui appelle flag.Parse. Ainsi, tous les drapeaux 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écutant go test -tags=unit.

@adamc commentaires:

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

En outre, la balise utilisée dans le commentaire de construction ne peut pas comporter de tiret, bien que les traits de soulignement soient autorisés. Par exemple, // +build unit-tests ne fonctionnera pas, alors que // +build unit_tests fonctionnera.

98
Alex

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

La seconde consiste à utiliser une convention et à appeler vos tests TestUnitFoo ou TestIntegrationFoo, puis à utiliser le drapeau -run testing pour indiquer les tests à exécuter. Donc, vous utiliseriez go test -run 'Unit' pour les tests unitaires et go 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 go test simple pour les tests unitaires et FOO_TEST_INTEGRATION=true go test pour les tests d'intégration.

Personnellement, je préférerais la solution -short car elle est plus simple et est 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 -run et os.Getenv offrent plus de flexibilité (il faut également faire preuve de prudence, car les expressions rationnelles interviennent dans -run).

39
Ainar-G

Pour développer mon commentaire sur l'excellente réponse de @ Ainar-G, au cours de la dernière année, j'ai utilisé la combinaison de la convention de nommage -short avec Integration pour obtenir le meilleur des deux mondes. 

L'unité et l'intégration teste l'harmonie dans le même fichier

Les drapeaux de construction m'obligeaient auparavant à avoir plusieurs fichiers (services_test.go, services_integration_test.go, etc.).

Au lieu de cela, prenons 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 Integration dans le nom du test.
  2. vérification si exécuté sous la directive -short flag.

En gros, la spécification dit: "écrivez tous les tests normalement. S'il s'agit d'un test de longue durée ou d'un test d'intégration, suivez cette convention de dénomination et vérifiez que -short est agréable à vos pairs."

Exécuter uniquement des tests unitaires:

go test -v -short

cela vous fournit une belle série 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 les tests de fumée canaris en production.

Évidemment, l'inconvénient de cette approche est que si quelqu'un exécute go test, sans l'indicateur -short, il exécutera par défaut tous les tests - tests unitaires et d'intégration.

En réalité, si votre projet est assez grand pour avoir des tests unitaires et d'intégration, vous utilisez probablement une variable Makefile dans laquelle vous pouvez avoir des directives simples pour utiliser go test -short. Ou bien, mettez-le simplement dans votre fichier README.md et appelez-le le jour même.

26
eduncan911

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

  • La solution doit être universelle
  • Pas de package séparé pour les tests d'intégration
  • La séparation devrait être complète (je devrais être capable de lancer des tests d'intégration seulement)
  • Pas de convention de nommage 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. C'est pourquoi, après un peu de fouille et de lecture, j'ai proposé 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, mais est moins sujet aux erreurs. Une autre amélioration pourrait consister à exporter le code vers une fonction d'assistance.

Usage

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

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

Exécutez tous les tests (regular et intégration):

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

N'exécutez que des tests réguliers:

go test -v ./...

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

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

1
mark.sagikazar