Gold Master Testing et xUnit.net

# gold master# xUnit.net# C## test# .Net
écrit par matthieu.mourisson publié le mercredi 22 novembre 2017

Lorsqu'on modifie ou qu'on réécrit un système ancien avec des calculs complexes, le risque de régression est élevé. Pour réduire ce risque, l'idéal est d'avoir une batterie de tests unitaires qui couvre les moindres recoins du système. Mais comment faire lorsque le système en question n'a pas ou peu de tests unitaires et que les personnes à l'origine du système ont changé de projet depuis longtemps ? C'est là qu'entre en jeu la méthode du "Gold Master Testing".

Introduction

Le gold master testing se fait en plusieurs étapes. La première étape consiste à définir un jeu de données à tester. Ce jeu de données peut être une copie de données de production (anonymisées de préférence) ou généré aléatoirement. Une fois qu'on a ce jeu de données, on le passe dans le système et on stocke les résultats ; ce sera notre gold master. L'étape suivante consiste à mettre en place des tests automatisés qui vérifient que les mêmes opérations sur notre jeu de données produisent des résultats identiques au gold master.

Exemple avec xUnit.net

Pour mettre en place les tests unitaires du gold master nous allons utiliser une "theory" du framework de test xUnit qui nous permet d'exécuter la même méthode de test sur des données multiples (cf. cet article du blog ). Dans l'exemple ci-dessous, on teste un interpréteur qui reçoit un programme en input et produit un tableau de byte en output. On a préalablement créé un gold master en générant une série de programmes valides qu'on a passés à notre interpréteur et dont on a enregistré le résultat dans le fichier "program corpus.txt". La fonction GetReferenceData va lire le fichier "program corpus.txt" et retourner un IEnumerable de tableaux d'objets contenant le titre du programme, le programme lui-même et le résultat attendu de l'exécution.
public static IEnumerable<object[]> GetReferenceData()
{
	using (var fileStream = File.OpenText("Resources\\program corpus.txt"))
	{
		string title = "";
		string program = "";

		int lineNumber = 0;
		while (!fileStream.EndOfStream)
		{
			var line = fileStream.ReadLine();

			if (lineNumber % 3 == 0)
				title = line;
			else if (lineNumber % 3 == 1)
				program = line;
			else
				yield return new object[] { title, program, Convert.FromBase64String(line) };

			lineNumber++;
		}
	}
}
La theory TestGoldMasterCorpus indique que sa source de données est la fonction GetReferenceData via l'attribut MemberData. On teste ensuite que l'interpréteur produit bien le même résultat que celui attendu dans le gold master.
[Theory]
[MemberData(nameof(GetReferenceData))]
public void TestGoldMasterCorpus(string title, string program, byte[] expectedBinaryOutput)
{
	var interpreter = new Interpreter(program);
	interpreter.Run();
	Assert.True(interpreter.EndOfProgram, $"The program didn't finished execution : {title}");
	Check.That(interpreter.BinaryOutput).ContainsExactly(expectedBinaryOutput);
}
Chaque cas de test retourné par la fonction GetReferenceData génère un test unitaire indépendant dans l'explorateur de tests. Ainsi, même lorsque que le premier cas de test échoue, les tests suivants sont bien exécutés. [caption id="attachment_592" align="alignnone" width="415"]Theory xUnit dans le test explorer Theory xUnit dans le test explorer[/caption]

Les limites

La méthode du gold master a plusieurs inconvénients. Premièrement, le gold master testing ne détecte pas de bugs, il ne détecte que les régressions. Si le code utilisé pour générer le gold master était buggé il faudra mettre à jour le gold master quand on corrigera ces bugs. Ensuite, il faut que le système testé ait un comportement de type "boite noire" qui prend des données en entrée et produit de nouvelles données en sortie. Un système qui gère beaucoup de données mais fait peu de calculs n'est pas du tout adapté au gold master. Il faut également que le système testé soit stable et éprouvé. Cela n’aurait pas beaucoup de sens de faire un gold master d’un système complètement buggé ou dont le développement vient de commencer. Enfin, il est préférable que les tests puissent s'exécuter dans un temps raisonnable. Si les tests mettent plusieurs heures à s'exécuter, il vaut mieux les sortir de la suite de tests unitaires et créer une tâche spécifique qui s'exécute toutes les nuits.