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?
@ 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.
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
).
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.
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:
Integration
dans le nom du test.-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."
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
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.
J'essayais de trouver une solution pour la même chose récemment . Ce sont mes critères:
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.
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