Tests unitaires de manière succincte comment fonctionnent les tests unitaires?

Ceci est un extrait de l'eBook Unit Testing Succinctly de Marc Clifton, gracieusement fourni par Syncfusion..

Un moteur de test unitaire dans un langage de réflexion (tel que n’importe quel langage .NET) se compose de trois parties:

  • Chargement d'assemblages contenant les tests.
  • Utiliser la réflexion pour trouver les méthodes de test.
  • Invoquer les méthodes et valider les résultats.

Cet article fournit des exemples de code montrant comment cela fonctionne, en mettant en place un moteur de test unitaire simple. Si vous n'êtes pas intéressé par l'enquête sous le capot des moteurs de test unitaire, n'hésitez pas à ignorer cet article.

Le code ici suppose que nous écrivons un moteur de test par rapport aux attributs de test d'unité Visual Studio définis dans l'assembly Microsoft.VisualStudio.QualityTools.UnitTestFramework. D'autres moteurs de test unitaire peuvent utiliser d'autres attributs aux mêmes fins..

Assemblages de chargement

Sur le plan architectural, vos tests unitaires doivent résider dans un assemblage distinct du code à tester ou, à tout le moins, ne doivent être inclus dans l'assemblage que s'il est compilé en mode «Debug». L'avantage de placer les tests unitaires dans un assemblage séparé est que vous pouvez également tester un peu la version de production optimisée du code sans mise au point.

Cela dit, la première étape consiste à charger l’ensemble:

static bool LoadAssembly (string nomFichier, assemblée final, problème lié à une chaîne sortante) bool ok = true; issue = String.Empty; assy = null; try assy = Assembly.LoadFile (assemblyFilename);  catch (Exception ex) issue = "Erreur lors du chargement de l'assembly:" + ex.Message; ok = faux;  retour ok 

Notez que les moteurs de test unitaires professionnels chargent les assemblages dans un domaine d'application distinct, de sorte que l'assemblage puisse être déchargé ou rechargé sans redémarrer le moteur de test unitaire. Cela permet également de recompiler l'ensemble de test unitaire et les ensembles dépendants sans arrêter le moteur de test d'unité en premier..

Utilisation de Reflection pour rechercher des méthodes de test unitaire

L'étape suivante consiste à réfléchir sur l'assemblage pour identifier les classes désignées comme «équipement de test» et, au sein de ces classes, pour identifier les méthodes de test. Un ensemble de base de quatre méthodes prend en charge les exigences minimales du moteur de test unitaire, la découverte de montages de test, les méthodes de test et les attributs de gestion des exceptions:

///  /// Retourne une liste de classes de l'assembly fourni ayant un attribut "TestClass". ///  IEnumerable statique GetTestFixtures (Assy Assembly) retourne assy.GetTypes (). Where (t => t.GetCustomAttributes (typeof (TestClassAttribute), false) .Length == 1);  ///  /// Retourne une liste des méthodes de la fixture de test qui sont décorées avec l'attribut "TestMethod". ///  IEnumerable statique GetTestMethods (Type testFixture) return testFixture.GetMethods (). Où (m => m.GetCustomAttributes (typeof (TestMethodAttribute), false) .Length == 1);  ///  /// Retourne une liste d'attributs spécifiques pouvant décorer la méthode. ///  IEnumerable statique GetMethodAttributes(Méthode MethodInfo) méthode de retour.GetCustomAttributes (typeof (AttrType), false) .Cast();  ///  /// Renvoie true si la méthode est décorée avec un attribut "ExpectedException" alors que le type d'exception est l'exception attendue. ///  static bool IsExpectedException (méthode MethodInfo, Exception attendueException) Type attendueExceptionType = attenduException.GetType (); retourne GetMethodAttributes(méthode). Where (attr => attr.ExceptionType == attendueExceptionType) .Count ()! = 0; 

Invocation de méthodes

Une fois ces informations compilées, le moteur appelle les méthodes de test dans un bloc try-catch (nous ne voulons pas que le moteur de test unitaire se bloque):

RunTests void statique (Type testFixture, Action résultat) IEnumerable testMethods = GetTestMethods (testFixture); if (testMethods.Count () == 0) // Ne faites rien s'il n'y a pas de méthodes de test. revenir;  object inst = Activator.CreateInstance (testFixture); foreach (MethodInfo mi dans testMethods) bool pass = false; try // Les méthodes de test n'ont pas de paramètres. mi.Invoke (inst, null); pass = true;  catch (Exception ex) pass = IsExpectedException (mi, ex.InnerException);  finally result (testFixture.Name + "." + mi.Name + ":" + (pass? "Pass": "Fail")); 

Enfin, nous pouvons rassembler ce code dans une application console simple qui prend l'assemblage de test unitaire en tant que paramètre pour obtenir un moteur utilisable mais simple:

en utilisant le système; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace SimpleUnitTestEngine class Programme static void Main (string [] args)) problème de chaîne; if (! VerifyArgs (arguments, problème sortant)) Console.WriteLine (problème); revenir;  Assemblée assy; if (! LoadAssembly (args [0], assy, ​​out issue))) Console.WriteLine (issue); revenir;  IEnumerable testFixtures = GetTestFixtures (assy); foreach (type testFixture dans testFixtures) RunTests (testFixture, t => Console.WriteLine (t));  static bool VerifyArgs (string [] args, problème de chaîne non résolue) bool ok = true; issue = String.Empty; if (args.Length! = 1) issue = "Utilisation: SimpleUnitTestEngine "; ok = false; else chaîne nomFichier = args [0]; if (! File.Exists (nomFichier assembly)) issue =" Le nom de fichier '"+ args [0] +"' n'existe pas. "; ok = false; retournez ok;… le reste du code… 

Le résultat de l'exécution de ce moteur de test simple est affiché dans une fenêtre de console, par exemple:

Résultats de notre console de moteur de test simple