Unit Testing Kurz gesagt Advanced Unit Testing

Dies ist ein Auszug aus dem Unit Testing Succinctly eBook von Marc Clifton, freundlicherweise von Syncfusion zur Verfügung gestellt.

In diesem Artikel werden wir über einige der fortgeschrittenen Themen sprechen, die mit dem Komponententest einhergehen. Dazu gehören Dinge wie zyklometrische Komplexität, Methoden, Felder und Reflexion.

Zyklometrische Komplexität

Die zyklometrische Komplexität ist ein Maß für die Anzahl unabhängiger Pfade durch Ihren Code. Das Testen der Codeabdeckung soll sicherstellen, dass Ihre Tests alle möglichen Codepfade ausführen. In den Test-, Premium- und Ultimate-Versionen von Visual Studio sind Tests zur Code-Abdeckung verfügbar. Die Codeabdeckung ist nicht Teil von NUnit. Eine Drittanbieterlösung, NCover, ist ebenfalls verfügbar.

Um dieses Problem zu veranschaulichen, betrachten Sie diesen Code, der Befehlszeilenparameter analysiert:

public static class CommandLineParser /// /// Gibt eine Liste von Optionen zurück, die auf der Brute-Force-Analyse /// von Befehlszeilenoptionen basieren. Die Funktion gibt die /// angegebenen Optionen und gegebenenfalls den Optionsparameter zurück. /// öffentliches statisches Wörterbuch Parse (string cmdLine) Wörterbuch Optionen = neues Wörterbuch(); 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;   

und ein paar einfache Tests (Beachten Sie, dass diese Tests die Codepfade auslassen, die dazu führen, dass Ausnahmen ausgelöst werden):

[TestFixture] öffentliche Klasse CodeCoverageTests [Test] öffentliche void CommandParserTest () Dictionary options = CommandLineParser.Parse ("- a -b"); Assert.That (options.Count == 2, "Anzahl erwartet 2"); Assert.That (options.ContainsKey ("- a"), "Erwartete Option '-a'"); Assert.That (options.ContainsKey ("- b"), "Erwartete Option '-b'");  [Test] public void FilenameParsingTest () Wörterbuch options = CommandLineParser.Parse ("- f foobar"); Bestätigen Sie das (options.Count == 1, "Anzahl erwartet 1")); Assert.That (options.ContainsKey ("- f"), "Erwartete Option '-f" "); Assert.That (Optionen ["- f"] == "foobar");  

Sehen wir uns nun an, wie ein Code-Coverage-Test aussehen könnte. Schreiben Sie zunächst den Code Coverage-Helper eines armen Mannes:

öffentliche statische Klasse Coverage öffentliche statische Liste CoveragePoints get; set; public static void Reset () CoveragePoints = neue Liste ();  [Bedingt ("DEBUG")] public static void Set (int coveragePoint) CoveragePoints.Add (coveragePoint);  

Wir benötigen auch eine einfache Erweiterungsmethode. Sie werden sehen, warum in einer Minute:

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

Jetzt können wir in unserem Code Coverage-Sollwerte hinzufügen, die in die Anwendung kompiliert werden, wenn sie im DEBUG-Modus kompiliert wird (die fetten roten Linien wurden hinzugefügt):

public static class CommandLineParser /// /// Gibt eine Liste von Optionen zurück, die auf der Brute-Force-Analyse /// von Befehlszeilenoptionen basieren. Die Funktion gibt die /// angegebenen Optionen und gegebenenfalls den Optionsparameter zurück. /// öffentliches statisches Wörterbuch Parse (string cmdLine) Wörterbuch Optionen = neues Wörterbuch(); 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;   

Und jetzt können wir das folgende Test-Fixture schreiben:

[TestFixture] öffentliche Klasse CommandParserCoverageTests [SetUp] public void CoverageSetup () Coverage.Reset ();  [Test] public void CommandParserTest () Wörterbuch options = CommandLineParser.Parse ("- a -b"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 1));  [Test] public void FilenameParsingTest () Wörterbuch options = CommandLineParser.Parse ("- f foobar"); Assert.That (Coverage.CoveragePoints.HasOrderedItems (new [] 1, 2));  

Beachten Sie, dass wir jetzt die tatsächlichen Ergebnisse ignorieren, aber sicherstellen, dass die gewünschten Codeblöcke ausgeführt werden.

White-Box-Test: Untersuchen geschützter und privater Felder und Methoden

Ein Unit-Test sollte sich nur mit öffentlichen Bereichen und Methoden befassen. Das Gegenargument dafür ist, dass, um die gesamte Implementierung zu testen, der Zugriff auf geschützte oder private Felder erforderlich ist, um ihren Zustand zu bestätigen, und die Fähigkeit, geschützte oder private Methoden zu testen, ist erforderlich. In Anbetracht dessen, dass es wahrscheinlich nicht wünschenswert ist, die meisten Low-Level-Berechnungen aufzudecken, und dies sind genau die Methoden, die getestet werden sollen, ist es höchst wahrscheinlich, dass zumindest geschützte oder private Methoden getestet werden müssen. Es stehen verschiedene Optionen zur Verfügung.

Methoden und Felder im Testmodus verfügbar machen

Dieses Beispiel veranschaulicht das Konzept:

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

Während dies machbar ist, wird hässlicher Quellcode erzeugt und es besteht das ernste Risiko, dass jemand die Methode tatsächlich mit dem definierten Symbol TEST aufruft, nur um festzustellen, dass sein Code in einem Produktions-Build bricht, bei dem das TEST-Symbol undefiniert ist.

Testklasse ableiten

Wenn die Methoden geschützt sind, können Sie alternativ eine Testklasse ableiten:

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

Auf diese Weise können Sie die abgeleitete Testklasse instantiieren und über eine öffentlich verfügbare Methode in der Unterklasse auf eine geschützte Methode zugreifen.

Reflexion

Schließlich kann Reflektion für private Methoden oder versiegelte Klassen verwendet werden. Im Folgenden wird eine private Methode veranschaulicht, die durch Reflexion in einem Test ausgeführt wird:

öffentliche Klasse DoesSomething private void SomeComputation ()  [TestClass] öffentliche Klasse DoesSomethingTest [TestMethod] öffentliche void SomeComputationTest () DoesSomething ds = new DoesSomething (); Geben Sie t = ds.GetType () ein. MethodInfo mi = t.GetMethod ("SomeComputation", BindingFlags.Instance | BindingFlags.NonPublic); mi.Invoke (ds, null);