Hör mal wer da testet – .Net Edition

Hallo Spaß Coder,

in den letzten Artikeln Testautomatisierung, Von Tests und Komponenten sowie Kein Unit-Test wie jeder andere haben wir euch heiß darauf gemacht, euren Code automatisiert auf funktionale Richtigkeit hin zu überprüfen. In diesem Artikel möchten wir euch ein paar Werkzeuge vorstellen, mit dessen Hilfe ihr euren .Net-Code automatisiert testen könnt. Wir setzen dabei wie immer C# als Sprache ein, die vorgestellten Werkzeuge funktionieren aber genauso gut mit VB.Net.

Für Werkzeuge, die ihr im Umfeld von Java verwenden könnt lest den Artikel Hör mal wer da testet – Java Edition.

Die hier verwendeten Beispiele testen eine Implementierung des Katas Russische Bauernmultiplikation aus dem Fundus der Coding Dojos der CCD.

 

NUnit

Mit Hilfe von NUnit können wir unseren Code einfach automatisch testen, ohne die Anwendung selbst starten zu müssen. Wir können Test-Code schreiben und darin die Methoden unserer Klasse aufrufen. NUnit führt diese Tests dann aus und bietet auch eine Möglichkeit, die Ergebnisse zu prüfen.

Das Visual Studio bindet NUnit mit Hilfe eines Plugins, die seit der Version 2013 auch in der kostenlosen Community Edition des VS verwendet werden können. Die Tests lassen sich dann mit einem Klick starten und zeigen die Ergebnisse des Testaufrufs komfortabel an. So zeigt ein grüner Balken, dass alle Tests erfolgreich durchlaufen wurden, ein roter, dass Tests fehlgeschlagen sind und ein gelber Balken zeigt an, dass Tests nicht ausgeführt wurden. Bei fehlgeschlagenen Tests sehen wir dann, wo der Test fehlschlägt und wir können uns sofort an die Behebung des Problems machen.

Beispiele:

Fangen wir mit einem einfachen Test an. Wir wollen testen, ob die Methode Mul 1×1 rechnen kann. Wir erwarten, dass dabei das Ergebnis 1 herauskommt.

Das Attribut [Test] kennzeichnet hier die Testmethode, wodurch NUnit diese Methode als ausführbaren Test erkennt. Als nächstes erstellen wir eine Instanz unserer zu testenden Klasse RussischeBauernmultiplikation. Wir haben uns angewöhnt die zu testende Variable sut zu nennen, als Abkürzung für System Under Test oder Subject Under Test (siehe auch Von Tests und Komponenten). Dadurch erkennen wir auch bei komplexeren Testmethoden sofort, welche Klasse was wir eigentlich testen.
In der letzten Zeile wird nun geprüft, durch unsere Behauptung (englisch: Assertion), dass 1×1 = 1 ist. Der erste Parameter der Methode AreEqual ist dabei unser erwartetes Ergebnis (1) und der zweite Parameter das tatsächliche Ergebnis des Methodenaufrufs unserer Testklasse.

Funktioniert der Test, zeigt NUnit einen grünen Balken an. Schlägt der Test hingegen fehl, wird der Balken rot und NUnit zeigt uns an, welcher Test fehlgeschlagen ist. Hier eine beispielhafte Ausgabe von NUnit, wenn die Methode Mul(1, 1) statt der erwarteten 1 eine 2 zurück gibt:

de.invidit.RussischeBauernmultiplikation.Test.RussischeBauernmultiplikationTests.OneTimesOneIsOne:
Expected: 1
But was:  2
bei de.invidit.RussischeBauernmultiplikation.Test.RussischeBauernmultiplikationTests.OneTimesOneIsOne() in \RussischeBauernmultiplikation.Test\RussischeBauernmultiplikationNUnit\RussischeBauernmultiplikationTests.cs:Zeile 15.

Wir sehen also, dass der Aufruf in Zeile 15 der Test-Klasse RussischeBauernmultiplikationTests zu einem Fehler führt.

Auch der Aufbau des Tests, wie wir ihn im Artikel Von Tests und Komponenten beschrieben haben ist hier erkennbar. Hier nochmal eine deutlichere Aufteilung am Beispiel des Tests 2×2 = 4:

Neben der zu testenden Klasse legen wir auch unser erwartetes Ergebnis in einer Variablen ab. Passend zum Parameternamen der assert-Methoden von NUnit nennen wir die Variable expected. Dann rufen wir unsere zu testende Methode auf, also Mul und speichern das Ergebnis in der Variable actual. Zum Schluss prüfen wir die Behauptung (assertion), ob unsere Erwartung (expected) mit dem tatsächlichen (actual) Ergebnis übereinstimmt.

Auf diese Weise lassen sich die meisten Methoden einfach testen.

Microsoft Unit Testing Framework

Microsoft Unit Testing Framework funktioniert genauso wie NUnit und ist seit Version 2012 fester Bestandteil der kostenlosen Editionen des Visual Studios (VS Express und seit 2013 VS Community). Die Syntax unterscheidet sich von NUnit, die Einbindung in die IDE ist aber genauso gut (siehe oben bei der Beschreibung von NUnit).

Beispiele:

Für den Test mit dem Microsoft Unit Testing Framework werden eigene Attribute zur Kennzeichnung von Testklasse und -methoden verwendet.

So ersetzen wir einfach das Attribute [Test] von NUnit durch [TestMethod]. Dazu lege ich ein MS-Test Projekt an, womit auch die erforderlichen Verweise korrekt vorhanden sind. Über das Menü Test -> Run -> All Tests lassen sich dann alle Test der Klasse ausführen. Das Ergebnis wird im TestExplorer angezeigt.

 

Fluent Assertions

Die Bibliothek FluentAssertions erweitert das verwendete Testframework um einen Aspekt, der unsere Unit-Tests lesbarer macht. So liest sich die Überprüfung unserer Behauptung wie wir sie oben formuliert haben ein wenig holprig:

“Behaupte sind gleich, erwartet wie tatsächlich.”

Hä? Wer redet denn so einen Quatsch?

Wäre es nicht schöner, wenn wir unsere Behauptung in etwa so formulieren könnten:

“Tatsächliches Ergebnis sollte gleich sein mit erwartetem Ergebnis.”

Das Klingt zugegebenermaßen immer noch ein wenig holprig. Versuchen wir das Ganze mal in Englisch:

“Actual should be expected.”

In unseren Ohren klingt das doch ganz gut. FluentAssertions hilft uns dabei, unsere Behauptungen genau so zu formulieren.

 

Beispiele:

Schauen wir uns dasselbe Beispiel wie oben an, nämlich dass 1 x 1 = 1 ist (sein sollte).

Hier ist die Lesbarkeit schon höher als oben beschrieben. Richtig spannend wird FluentAssertions in der Arbeit bestimmten Typen. Nachfolgend ein paar Ideen dazu:

  • Strings
    • BeNull / NotBeNull
    • BeEmpty / NotBeEmpty
    • HaveLength
    • StartWith / Contain / EndWith
  • Numerische Typen
    • Be / NotBe
    • BeGreaterOrEqualTo
    • BeLessThan
    • BeInRange
  • Collections
    • NoBeEmpty
    • HaveCount
    • StartWith / EndWith
    • BeSubsetOf
    • Contain / OnlyContain
  • Datum
    • BeAfter / BeBefore
    • HaveDay / HaveMonth / HaveYear
  • Ausnahmen
    • ShouldThrow
    • WithMessage

Darüber hinaus können  Behauptungen auch kombiniert werden. Grundsätzlich sollte immer nur ein Konzept innerhalb eines Tests geprüft werden, aber FluentAssertions bietet auch die Möglichkeit Behauptungen zu verknüpfen (AND / OR).

 

moq

Während wir mit den vorgenannten Werkzeuge grundsätzlich Unit-Tests schreiben können, hilft uns moq dabei unsere Tests von ggf. vorhandenen externen Einflüssen zu isolieren. Falls euch jetzt nicht klar sein sollte, was wir damit meinen, lest noch einmal den Artikel Von Tests und Komponenten. Hier wird auf die Notwendigkeit und die unterschiedlichen Möglichkeiten eingegangen.

Beispiele:

moq ist ein sehr mächtiges Werkzeug. In diesem Rahmen möchten wir euch die wichtigsten Anwendungsfälle zur Isolation erläutern, das Stubbing.

Wir rufen eine Methode auf und können mit Hilfe von moq festlegen, welchen Wert diese Methode zurückgeben soll. Der folgende Test wird erfolgreich durchlaufen:

Die Methode Mul(1, 3) unserer Implementierung gibt den Wert 3 zurück (1×3 = 3). Wir haben moq auf Basis des Interfaces aber folgendes gesagt:

“Wenn die Methode Mul mit den Parametern 1 und 3 aufgerufen wird, gebe den Wert 5 zurück.”

Damit wir das überhaupt tun können, müssen wir zunächst mit Hilfe von moq einen mock der Klasse erstellen, dessen Methodenrückgabe wir verändern möchten. In unserem obigen Fall handelt es sich dabei um ein Interface, es kann aber auch eine Klasse sein.

Wichtig zu wissen ist, dass bei einem Mock immer nur die Methoden einen Wert zurückgeben, die wir auch konfiguriert haben. Ergänzen wir noch folgenden Assert, wird der Test fehlschlagen:

Test Name:    testMockitoSimpleWhenThenReturn
Test FullName:    TestExamples.MoqExamples.testMockitoSimpleWhenThenReturn
Test Outcome:    Failed
Test Duration:    0:00:00,1369741

Result Message:    Expected 1, but found 0.

Die Methode Mul haben wir nur für die Parameter (1, 3) konfiguriert. Rufen wir sie mit (1, 1) auf, wird der Initialwert des Rückgabetyps (in unserem Fall int) zurückgegeben (dieser ist bei int 0).

Neben dem beschriebenen Stubbing wird beim Mocking nicht das eigentliche Testobjekt geprüft, sondern ob die Aufrufe auf dem Mock-Objekt wie erwartet erfolgt sind.

Damit wird in unserem Beispiel die Methode Roll() indirekt geprüft. Dies kann dann sinnvoll sein, wenn geprüft wird, ob der Aufruf einer Abhängigkeit mit korrekten Parametern erfolgt.

 

Matcher

Möchten wir Methodenaufrufe mit beliebigen Parametern konfigurieren, müssen wir sog. Matcher verwenden:

Die Klasse Moq.It stellt verschiedene Matcher zur Verfügung, welche zur Konfiguration von Stubs verwendet werden können.

Ein “echtes” Beispiel

Die oben aufgeführten Beispiele sind natürlich nicht sinnvoll, sie dienen nur dem Zweck, die Funktionsweise von moq leicht verständlich zu beschreiben. Hilfreich ist ein Mock immer dann, wenn wir unsere Klasse von Abhängigkeiten trennen wollen.

Nehmen wir als Beispiel den Test der Klasse “Würfel”, dessen Methode “würfeln” einen zufälligen Wert verwendet:

Im Grunde ist die Methode nicht testbar, da hier immer andere Ergebnisse herauskommen, wenn von der Klasse Random die Methode Next() aufgerufen wird. Mit Hilfe von moq können wir die Methode aber trotzdem testen:

Dieser Test funktioniert. In dem wir festlegen, dass beim Aufruf von Next() immer ein fester Wert zurückkommt, können wir testen, ob die Methode Roll() die Summe der Einzelwürfe korrekt addiert.

 

Unsere Erfahrung mit den Werkzeugen

Wir setzen insbesondere Microsoft Unit Testing Framework und Fluent Assertions ein, wenn wir unsere .Net-Projekte entwickeln. Früher haben wir NUnit verwendet, da Microsoft Unit Testing Framework in den Express-Editionen des Visual Studios nicht zur Verfügung stand. Da Fluent Assertion beide Frameworks unterstützt und Microsoft Unit Testing Framework mittlerweile immer „an Bord“ ist, verwenden wir dies aus reiner Bequemlichkeit. Sie leisten uns gute Dienste und unterstützen dabei, den Code abzusichern. Mit moq haben wir erst sehr wenig Erfahrung gesammelt und faktisch bislang in keinem Projekt eingesetzt. Wir haben unsere Mocks bislang immer manuell erstellt. Das werden wir aber in Zukunft sicher ändern, da es ein großartiges Werkzeug ist.

 

Zusammenfassung

Mit Hilfe der hier vorgestellten Werkzeuge und Beispiele solltet ihr in der Lage sein, euren Code elegant und automatisiert testen zu können. NUnit und Microsoft Unit Testing Framework bilden die Basis aller Unit-Tests. Fluent Assertions erweitert – die bereits sehr elegante Schreibweise von NUnit seit Version 2.4 (Constraint-Based Assert Model) – und Microsoft Unit Testing Framework um nützliche Funktionen und sinnvolle, einfach lesbare Syntax. moq hilft dabei, Tests von der umliegenden Infrastruktur zu trennen und auch Legacy-Code testbar zu machen ohne gleich die gesamte Code-Basis auf den Kopf stellen zu müssen.

Wir hoffen, dass es euch hiernach leichter fällt, euren sauberen Code auch funktional abzusichern.

Viel Spaß beim Anwenden.

Eure Spaß-Coder

 

Weitere Infos zu den Bibliotheken:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.