Comment déboguer la seconde exécution d'un test ?

Delve + GoLand + 2 = ?
Crédit image: fgm@osinet.fr

Certains tests n’échouent qu’à la seconde exécution, mais le déboguage intégré dans GoLand ne les exécute qu’une seule fois par défaut.

Comment faire pour les déboguer?

Le problème des tests non indépendants

Dans un code idéal, les tests unitaires sont bien isolés, et leurs dépendances sont initialisées correctement à chaque itération.

Mais dans le mode réel, ce n’est pas toujours le cas, et un test peut parfaitement réussir à la première exécution, n’alertant pas sur un problème sous-jacent, jusqu’au jour où un testeur plus curieux exécute la suite de test dans un ordre différent (avec go test -shuffle) ou plusieurs fois (avec go test -count=2), et découvre le pot aux roses.

Une raison pour laquelle cela peut arriver est l’utilisation d’une variable globale dans un package importé, dont la valeur va persister d’un test à l’autre, avec pour effet un comportement différent selon l’ordre des tests, ou le fait que la suite est exécutée une deuxième fois.

On obtient des situations où une exécution normale va réussir, mais une autre échouer pour la même suite:

  • go test → succès
  • go test -count=2 → succès à la première passe, échec à la seconde
  • go test -shuffle=1 → succès certaines fois, échec d’autres fois

À ce stade, comment déboguer la situation avec le débogueur Delve intégré à GoLand ?

Configuration de déboguage par défaut

Pour déboguer un test dans GoLand, il suffit de cliquer sur le triangle vert situé à côté du nom du test concerné dans l’éditeur, et de choisir l’option de déboguage comme dans l’image suivante:

popup de configuration d’exécution de test GoLand

Cela va créer une configuration d’exécution par défaut, et lancer l’exécution en mode déboguage.

Le problème est que cette exécution n’aura lieu qu’une seule fois, ce qui ne mettra pas en évidence le problème.

Tentative: go test -count=2

Comment cela se passe-t-il sur la ligne de commande ? Dans l’exemple ci-dessus, on utiliserait par exemple une syntaxe comme:

go test -count=2 -run=TestCountNumbers .

En examinant la configuration d’exécution qui vient d’être créée dans GoLand lors de notre premier essai, nous trouvons bien un endroit où ajouter des arguments à go test:

Hélas, à l’exécution, le test continue à n’être lancé qu’une seule fois.

Que se passe-t-il ? L’explication apparaît en dépliant le <4 go setup calls> pour voir comment GoLand exécute les tests dans cette configuration:

Dans ces configurations, comme l’illustre la copie d’écran précédente, GoLand exécute les tests un peu différemment :

  • il commence par construire un exécutable de test avec les options:
    • -c pour produire un binaire de test stocké au lieu de le lancer à la volée
    • -o <chemin> pour le placer dans un endroit privé
  • dans un deuxième temps, il lance go tool test2json sur le lanceur de Delve, en passant au programme construit à la première étape quelques drapeaux interceptés par le TestMain autogénéré ou manuel s’il existe:
    • -test.v pour afficher tous les tests
    • -test.paniconexit0 pour renvoyer une panic si un test exécute os.Exit(0)
    • -test.run ^\QTestCountNumbers\E$ pour exécuter uniquement le test choisi
    • … mais rien d’autre: l’argument -count=2 a été supprimé dans l’opération.

Lors d’un lancement en mode exécution plutôt que déboguage, le mécanisme est le même, à ceci près que GoLand lance le binaire de test depuis go tool test2json au lieu de le faire lancer par Delve.

La solution

Observons la façon dont GoLand transforme les arguments:

  • il produit le binaire de test, utilisant potentiellement les arguments de go test qui pourraient s’appliquer à cette étape, ce qui n’est pas le cas de -count
  • il lance go tool test2json puis sépare les drapeaux et arguments destinés au binaire de ceux qui pourraient être destinés à go tool lui-même par le délimiteur --

Or si nous réexaminons la configuration de test, en-dessous du champ pour les Go tool arguments destinés à go test, nous avons un autre champ destiné à passer au programme lui-même.

Nous pouvons donc passer le drapeau count de go test sous la forme d’un drapeau test.count du programme de test, de la même façon que GoLand ajoutait test.v et autres:

Et cette fois on obtient bien une exécution double du test problématique :

Il ne reste plus qu’à armer ses points d’arrêt et à déboguer. Bon courage !

Merci à Daniil Maslov pour avoir suggéré d’utiliser ce champ.