Passer au contenu principal
eLearner.app
Module 9 · Leçon 2 sur 542/50 dans le cours~14 min
Leçons du module (2/5)

Tests pilotés par les données

Les tests basés sur des tables sont le modèle idiomatique de Go : au lieu d'écrire N fonctions TestQuestoCasoSpecifico, vous déclarez une tranche de cas et d'itérations. Le code d'assertion n'est qu'UN, les données de test sont distinctes de la logique.

Le schéma de base

Go
func TestAbs(t *testing.T) {
    cases := []struct {
        name string
        in   int
        want int
    }{
        {"pos", 3, 3},
        {"neg", -3, 3},
        {"zero", 0, 0},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Abs(tc.in); got != tc.want {
                t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
            }
        })
    }
}

Trois ingrédients :

  1. Tranche de structure anonyme avec des champs typiques : name, les entrées, la sortie attendue (et peut-être une erreur attendue).
  2. for _, tc := range cases — la boucle d'exécution.
  3. t.Run(tc.name, func(t *testing.T) { ... }) — chaque cas devient un sous-test avec son propre nom.

Pourquoi t.Run ?

t.Run crée un sous-test qui a :

  • Nom individuel affiché dans le résultat : TestAbs/pos, TestAbs/neg, ...
  • t *testing.T proprement dit : une panne dans un cas ne bloque pas les autres (sauf si t.Fatal).
  • Filtre de ligne de commande : go test -run TestAbs/neg pour exécuter ce cas uniquement.
  • Parallélisme facultatif : appelez t.Parallel() à l'intérieur de la fonction pour paralléliser.
Bash
go test -v -run TestAbs/neg

Cas avec erreur attendue

Go
cases := []struct {
    name    string
    in      string
    want    int
    wantErr bool
}{
    {"ok", "42", 42, false},
    {"bad", "x", 0, true},
}
for _, tc := range cases {
    t.Run(tc.name, func(t *testing.T) {
        got, err := Atoi(tc.in)
        if (err != nil) != tc.wantErr {
            t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
        }
        if !tc.wantErr && got != tc.want {
            t.Errorf("got %d, want %d", got, tc.want)
        }
    })
}

Piège de la fermeture de boucle (avant Go 1.22)

Dans les versions antérieures à Go 1.22, la variable tc était partagée entre les itérations : l'appel de t.Parallel() à l'intérieur de t.Run entraînait l'exécution de tous les sous-tests sur le même dernier cas. La solution était :

Go
for _, tc := range cases {
    tc := tc // rebind locale (pre-1.22)
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

Dénomination des cas

Bonnes pratiques :

  • Nom court mais évocateur : "empty input", "too large", "negative".
  • Pas d'espace si vous souhaitez filtrer facilement avec -run (ou utiliser des traits de soulignement).
  • Incluez toujours un cas "happy path", un cas "edge" (zéro, vide) et au moins un cas d'erreur.

Exercices

Exercice#go.m9.l2.e1
Tentatives : 0Chargement…

Définissez une structure anonyme de tranches de cas avec les champs in et Want, remplie de 3 cas pour tester Abs (positif, négatif, zéro).

Chargement de l'éditeur…

Solution disponible après 3 tentatives

Exercice#go.m9.l2.e2
Tentatives : 0Chargement…

Ajoutez t.Run avec tc.name pour transformer chaque cas en un sous-test nommé.

Chargement de l'éditeur…

Solution disponible après 3 tentatives

Quiz#go.m9.l2.e3
Prêt

Quel est le principal avantage du modèle piloté par table par rapport à N fonctions de test distinctes ?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
Options de réponse