Tests unitaires succinctement Tests unitaires avancés

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

Dans cet article, nous allons commencer à parler de quelques-uns des sujets avancés fournis avec les tests unitaires. Cela inclut des éléments tels que la complexité cyclométrique, les méthodes, les champs et la réflexion..

Complexité cyclométrique

La complexité cyclométrique est une mesure du nombre de chemins indépendants dans votre code. Les tests de couverture de code ont pour but de garantir que vos tests exécutent tous les chemins de code possibles. Le test de couverture de code est disponible dans les versions Test, Premium et Ultimate de Visual Studio. La couverture de code ne fait pas partie de NUnit. Une solution tierce, NCover, est également disponible..

Pour illustrer ce problème, considérons ce code qui analyse les paramètres de ligne de commande:

public static class CommandLineParser /// /// Retourne une liste d'options basée sur l'analyse brutale /// des options de ligne de commande. La fonction retourne les /// options spécifiées et le paramètre option si nécessaire. /// Dictionnaire statique public Parse (string cmdLine) Dictionnaire options = nouveau dictionnaire(); string [] items = cmdLine.Split ("); int n = 0; while (n < items.Length)  string option = items[n]; string param = String.Empty; if (option[0] != '-')  throw new ApplicationException("Expected an option.");  if (option == "-f")  // Has the parameter been supplied? if (items.Length <= n + 1)  throw new ApplicationException("Filename not specified.");  param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-')  throw new ApplicationException("Filename not specified.");  ++n; // Skip the filename option parameter.  options[option] = param; ++n;  return options;   

et quelques tests simples (notez que ces tests omettent les chemins de code qui génèrent des exceptions):

[TestFixture] public class CodeCoverageTests [Test] public void CommandParserTest () Dictionary options = CommandLineParser.Parse ("- a -b"); Assert.That (options.Count == 2, "Le nombre devrait être égal à 2"); Assert.That (options.ContainsKey ("- a"), "Option attendue '-a'"); Assert.That (options.ContainsKey ("- b"), "Option attendue '-b'");  [Test] public void FilenameParsingTest () Dictionnaire options = CommandLineParser.Parse ("- f foobar"); Assert.That (options.Count == 1, "Le nombre devrait être 1"); Assert.That (options.ContainsKey ("- f"), "Option attendue '-f'"); Assert.That (options ["- f"] == "foobar");  

Voyons maintenant à quoi pourrait ressembler un test de couverture de code, tout d'abord en écrivant l'aide de couverture de code d'un homme pauvre:

public static class Coverage liste statique publique CoveragePoints get; set; public static void Reset () CoveragePoints = new List ();  [Conditional ("DEBUG")] public statique void Set (int coveragePoint) CoveragePoints.Add (coveragePoint);  

Nous aurons également besoin d'une méthode d'extension simple; vous verrez pourquoi dans une minute:

public static class ListExtensions public static bool HasOrderedItems (cette liste itemList, int [] éléments) int n = 0; foreach (int i dans itemList) if (i! = items [n]) return false;  ++ n;  return true;  

Nous pouvons maintenant ajouter des points de consigne de couverture dans notre code, qui seront compilés dans l'application lorsqu'elle sera compilée en mode DEBUG (les lignes rouges en gras où elles ont été ajoutées):

public static class CommandLineParser /// /// Retourne une liste d'options basée sur l'analyse brutale /// des options de ligne de commande. La fonction retourne les /// options spécifiées et le paramètre option si nécessaire. /// Dictionnaire statique public Parse (string cmdLine) Dictionnaire options = nouveau dictionnaire(); string [] items = cmdLine.Split ("); int n = 0; while (n < items.Length)  Coverage.Set(1); // WE ADD THIS COVERAGE SET-POINT string option = items[n]; string param = String.Empty; if (option[0] != '-')  throw new ApplicationException("Expected an option.");  if (option == "-f")  Coverage.Set(2); // WE ADD THIS COVERAGE SET-POINT // Has the parameter been supplied? if (items.Length <= n + 1)  throw new ApplicationException("Filename not specified.");  param = items[n + 1]; // Is it a parameter or another option? if (param[0] == '-')  throw new ApplicationException("Filename not specified.");  ++n; // Skip the filename option parameter.  options[option] = param; ++n;  return options;   

Et maintenant, nous pouvons écrire le montage de test suivant:

[TestFixture] classe publique CommandParserCoverageTests [SetUp] public void CoverageSetup () Coverage.Reset ();  [Test] public void CommandParserTest () Dictionary options = CommandLineParser.Parse ("- a -b"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 1)));  [Test] public void FilenameParsingTest () Dictionnaire options = CommandLineParser.Parse ("- f foobar"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 2)));  

Notez comment nous ignorons maintenant les résultats réels, mais nous nous assurons que les blocs de code souhaités sont exécutés.

Test de la boîte blanche: inspection des champs et des méthodes protégés et privés

On peut soutenir qu'un test unitaire ne devrait concerner que des champs et des méthodes publics. Le contre-argument pour cela est que pour tester toute la mise en œuvre, l'accès aux champs protégés ou privés, pour affirmer leur état, et la possibilité de tester à l'unité les méthodes protégées ou privées sont nécessaires. Considérant qu'il n'est probablement pas souhaitable d'exposer la plupart des calculs de bas niveau, et que ce sont exactement les méthodes que l'on souhaite tester, il est fort probable que le test de méthodes protégées ou privées soit nécessaire. Il y a plusieurs options disponibles.

Méthodes et champs d'exposition en mode test

Cet exemple illustre le concept:

classe publique DoesSomething #if TEST public #else private #endif void SomeComputation ()  

Bien que cela soit faisable, cela produit un code source laid et présente le risque sérieux que quelqu'un appelle la méthode avec le symbole TEST défini, mais découvre que son code est rompu dans une version de production où le symbole TEST n'est pas défini..

Dériver une classe de test

En guise d'alternative, si les méthodes sont protégées, envisagez de dériver une classe de test:

classe publique DoesSomethingElse protégé void SomeComputation ()  classe publique DoesSomethingElseTesting: DoesSomethingElse public void TestSomeComputation () base.SomeComputation ();  

Cela vous permet d’instancier la classe de test dérivée et d’accéder à une méthode protégée via une méthode exposée publiquement dans la sous-classe..

Réflexion

Enfin, on peut utiliser la réflexion pour des méthodes privées ou des classes scellées. Ce qui suit illustre une méthode privée et l’exécution de cette méthode via une réflexion dans un test:

classe publique DoesSomething private void SomeComputation ()  [TestClass] classe publique DoesSomethingTest [TestMethod] public void SomeComputationTest () DoesSomething ds = new DoesSomething (); Tapez t = ds.GetType (); MethodInfo mi = t.GetMethod ("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke (ds, null);