Eenheidstests bondig geavanceerde unit-testen

Dit is een uittreksel uit het Unit Testing Beknopte eBook, door Marc Clifton, vriendelijk geleverd door Syncfusion.

In dit artikel gaan we het hebben over enkele van de geavanceerde onderwerpen die horen bij het testen van eenheden. Dit omvat zaken als cyclometrische complexiteit, methoden, velden en reflectie.

Cyclometrische complexiteit

Cyclometrische complexiteit is een maat voor het aantal onafhankelijke paden door uw code. Codedekkingstests zijn bedoeld om ervoor te zorgen dat uw tests alle mogelijke codepaden uitvoeren. Codedekkingstests zijn beschikbaar in de test-, premium- en ultieme versies van Visual Studio. Codedekking maakt geen deel uit van NUnit. Een externe oplossing, NCover, is ook beschikbaar.

Om dit probleem te illustreren, overweeg deze code, die de parameters van de opdrachtregel parseert:

public static class CommandLineParser /// /// Retourneert een lijst met opties op basis van brute force-parsing /// van opdrachtregelopties. De functie retourneert de /// opgegeven opties en de optieparameter indien nodig. /// public static Dictionary Parse (string cmdLine) Woordenboek options = new Dictionary(); 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;   

en een paar eenvoudige tests (merk op dat deze tests de codepaden weglaten die tot gevolg hebben dat uitzonderingen worden gegenereerd):

[TestFixture] public class CodeCoverageTests [Test] public void CommandParserTest () Woordenboek options = CommandLineParser.Parse ("- a -b"); Assert.That (options.Count == 2, "Count verwacht 2" te zijn); Assert.That (options.ContainsKey ("- a"), "Expected option '-a'"); Assert.That (options.ContainsKey ("- b"), "Expected option '-b'");  [Test] public void FilenameParsingTest () Woordenboek options = CommandLineParser.Parse ("- f foobar"); Assert.That (options.Count == 1, "Count wordt naar verwachting 1"); Assert.That (options.ContainsKey ("- f"), "Expected option '-f'"); Assert.That (options ["- f"] == "foobar");  

Laten we nu eens kijken naar hoe een codedekkingstest eruit zou kunnen zien, eerst door een codewoordhelper voor arme mensen te schrijven:

openbare statische klasse Dekking public static List CoveragePoints get; set; public static void Reset () CoveragePoints = new List ();  [Voorwaardelijk ("DEBUG")] public static void Set (int coveragePoint) CoveragePoints.Add (coveragePoint);  

We hebben ook een eenvoudige uitbreidingsmethode nodig; je zult zien waarom in een minuut:

public static class ListExtensions public static bool HasOrderedItems (dit item ListList, int []) int n = 0; foreach (int i in itemList) if (i! = items [n]) return false;  ++ n;  return true;  

Nu kunnen we dekkingsinstellingen toevoegen in onze code, die in de toepassing wordt gecompileerd wanneer deze wordt samengesteld in de DEBUG-modus (de vetgedrukte rode lijnen zijn toegevoegd):

public static class CommandLineParser /// /// Retourneert een lijst met opties op basis van brute force-parsing /// van opdrachtregelopties. De functie retourneert de /// opgegeven opties en de optieparameter indien nodig. /// public static Dictionary Parse (string cmdLine) Woordenboek options = new Dictionary(); 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;   

En nu kunnen we de volgende testopstelling schrijven:

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

Merk op hoe we nu de daadwerkelijke resultaten negeren, maar zorgen ervoor dat de gewenste codeblokken worden uitgevoerd.

White Box Testing: het inspecteren van beschermde en private velden en methoden

Een eenheidstest zou aantoonbaar alleen betrekking moeten hebben op openbare velden en methoden. Het tegenargument hiervoor is dat om de volledige implementatie te testen toegang tot beveiligde of private velden, om hun status te bevestigen, en de mogelijkheid om geteste of privé-methoden te testen. Gezien het feit dat het waarschijnlijk niet wenselijk is om de meeste laag-niveau berekeningen bloot te leggen, en dat zijn precies de methoden die men wil testen, is het zeer waarschijnlijk dat het testen van ten minste beschermde of privé-methoden noodzakelijk is. Er zijn verschillende opties beschikbaar.

Belichtingsmethoden en velden in testmodus

Dit voorbeeld illustreert het concept:

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

Hoewel dit uitvoerbaar is, produceert het lelijke broncode en loopt het serieuze risico dat iemand de methode feitelijk met het symbool TEST definieert, alleen om te ontdekken dat zijn of haar code breekt in een productie-build waarbij het TEST-symbool niet gedefinieerd is.

Een testklasse afleiden

Als alternatief, als de methoden worden beschermd, overweeg dan om een ​​testklasse af te leiden:

public class DoesSomethingElse protected void SomeComputation ()  public class DoesSomethingElseTesting: DoesSomethingElse public void TestSomeComputation () base.SomeComputation ();  

Hiermee kunt u de afgeleide testklasse instantiëren en toegang krijgen tot een beveiligde methode via een openbaar blootgestelde methode in de subklasse.

Reflectie

Ten slotte kan men reflectie gebruiken voor privémethoden of verzegelde klassen. Het volgende illustreert een privémethode en voert die methode uit via reflectie in een test:

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