Il y a quelques tests pour lesquels nous avons énormément de lignes d'assertions. Nous allons les retravailler afin de les transformer en Approval Tests.
Prendre du temps pour comprendre ce qui se cache derrière cette notion d'Approval Testing
Identifier des tests sur lesquels on pourrait utiliser cette technique
Refactorer un test existant en utilisant la librairie Verify
Identification des tests
On pourrait utiliser cette technique pour les tests suivants :
Cela évitera de stocker ce string dans le code (sera stocké sous forme de ressource)
Refactorer ScenarioTests.DéroulerUnePartie
On commence par ajouter la dépendance sur notre librairie d'Approval Testing
dotnetaddpackageVerify.xUnit
On peut ensuite extraire le contenu de l'assertion dans un fichier "Approved" ici "verified"
On crée un fichier appelé : ScenarioTests.DéroulerUnePartie.verified.txt ([Nom de la classe de tests].[Nom du test].verified.txt)
C'est sur base de ce fichier que l'assertion se fera via Verify
On transforme le test en Approval Test en
Ajoutant l'annotation UsesVerify sur la classe de test
Changeant la méthode de test pour que celle-ci renvoie une Task
[UsesVerify]publicclassScenarioTests{ [Fact]public Task DéroulerUnePartie() {var time =newDateTime(2024,4,25,9,0,0);var repository =newPartieDeChasseRepositoryForTests();var service =newPartieDeChasseService(repository, () => time);var chasseurs =newList<(string,int)> { ("Dédé",20), ("Bernard",8), ("Robert",12) };var terrainDeChasse = ("Pitibon sur Sauldre",4);var id =service.Demarrer( terrainDeChasse, chasseurs ); time =time.Add(TimeSpan.FromMinutes(10));service.Tirer(id,"Dédé"); time =time.Add(TimeSpan.FromMinutes(30));service.TirerSurUneGalinette(id,"Robert"); time =time.Add(TimeSpan.FromMinutes(20));service.PrendreLapéro(id); time =time.Add(TimeSpan.FromHours(1));service.ReprendreLaPartie(id); time =time.Add(TimeSpan.FromMinutes(2));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromMinutes(1));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromMinutes(1));service.TirerSurUneGalinette(id,"Dédé"); time =time.Add(TimeSpan.FromMinutes(26));service.TirerSurUneGalinette(id,"Robert"); time =time.Add(TimeSpan.FromMinutes(10));service.PrendreLapéro(id); time =time.Add(TimeSpan.FromMinutes(170));service.ReprendreLaPartie(id); time =time.Add(TimeSpan.FromMinutes(11));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromSeconds(1));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromSeconds(1));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromSeconds(1));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromSeconds(1));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromSeconds(1));service.Tirer(id,"Bernard"); time =time.Add(TimeSpan.FromSeconds(1));try {service.Tirer(id,"Bernard"); }catch (TasPlusDeBallesMonVieuxChasseALaMain) { } time =time.Add(TimeSpan.FromMinutes(19));service.TirerSurUneGalinette(id,"Robert"); time =time.Add(TimeSpan.FromMinutes(30));service.TerminerLaPartie(id); // retourne le résultat de la méthode `Verify`returnVerify(service.ConsulterStatus(id)); }}
Le test passe du premier coup 👌
On va faire en sorte de le faire passer au rouge : ne jamais croire un test qu'on a pas vu échouer...
Pour cela le plus simple est de changer le fichier verified.
Notre Approval Test échoue, notre outil de comparaison de fichier va s'ouvrir :
Dès lors nous avons une arborescence de fichiers ressemblant à celà :
Un élément important quand on utilise une librairie de ce genre, ajouter les fichiers received dans le fichier .gitignore :
# Verify
*.received.txt
Félicitations, notre premier test passe et on peut se fier à lui.
En revanche, le test n'est pas très lisible / maintenable :
var command = DémarrerUnePartieDeChasse() .Avec(("Dédé",20), ("Bernard",8), ("Robert",12)) .SurUnTerrainRicheEnGalinettes(4);var id =_service.Demarrer(command.Terrain,command.Chasseurs);
On va ensuite supprimer la duplication en faisant une extraction des constantes : Bernard, Robert, Dédé, ChasseurInconnu
Pour pouvoir les utiliser dans cette classe de test également
On va extraire une méthode à partir de cela en identifiant les similitudes et différences
// Ajoute du temps à _time_time =_time.Add(TimeSpan.FromMinutes(30));// Appelle d'une méthode sur le service_service.TirerSurUneGalinette(id,Data.Robert);// Ajoute du temps à _time_time =_time.Add(TimeSpan.FromMinutes(20));// Appelle d'une méthode sur le service_service.PrendreLapéro(id);
On prépare notre extraction en décomposant le code ci-dessus en :
Par défaut, Verify va scrubber les donées non déterministes (DateTime et Guid ici)
Concernant la date, on perd 1 assertion faites dans le test avant refactoring
On change la configuration pour ce test
[Fact]publicTaskAvecPlusieursChasseurs(){var command = DémarrerUnePartieDeChasse() .Avec((Data.Dédé,20), (Data.Bernard,8), (Data.Robert,12)) .SurUnTerrainRicheEnGalinettes();PartieDeChasseService.Demarrer(command.Terrain,command.Chasseurs );returnVerify(Repository.SavedPartieDeChasse()) // On précise qu'on ne veut pas "scubber" les DateTime .DontScrubDateTimes();}
On peut maintenant approver le résultat du test qui ressemble à cela
Impact du refactoring des tests
Codescene
Après les refactorings des tests, on peut lancer une analyse codescene pour vérifier leur impact sur l'état de notre code base :
Nous sommes passé d'une Code Health de 8,4 à 9,8 👌
Les hotspots ont changé de taille (rouges car les commits sont très récents)
Il reste 1 refactoring target : PartieDeChasseService