Unittests sind für die Entwicklung von Software unerlässlich. Mit ihnen lassen sich Fehler vermeiden, welche sonst erst spät auffallen würden und schwer zu lokalisieren wären. Für C# Entwickler bietet Visual Studio einige Extensions, die uns bei der Erstellung von Unittests unterstützen.
In diesem Blogeintrag führe ich dich durch die wichtigsten Schritte beim Einrichten eines Testprojektes: Anhand eines Beispiels erstelle ich einen einfachen Unittest, gebe einige nützliche Tipps und stelle hilfreiche Tools vor. Die Voraussetzung für leserliche und effektive Unittests ist das die einzelnen Komponenten des Codes unabhängig vom Rest der Software testbar sind.
Wie du deinem Code mithilfe von Interfaces testbar machst, erfährst du in meinem nächsten Blogeintrag „Interfaces für besseres Unittesten“.
Warum sollte man Unittests durchführen?
- Sicherstellung der Funktionalität von Klassen und Methoden.
- Vereinfacht die Suche nach Fehlern, dar bestimmte Abschnitte des Codes durch Unittests abgedeckt sind und als Fehlerquelle ausgeschlossen werden können.
- Nach Anpassungen von Methoden oder Klassen lässt sich einfach und schnell feststellen, ob die Funktionalität noch gegeben ist.
Grundlegendes zu Unittests
Beim Erstellen von Unittests ist folgendes zu beachten:
- Um sinnvolle Unittests erstellen zu können müssen die einzelnen Komponenten unabhängig voneinander testbar sein. Wie Sie die Testbarkeit ihres Codes mit Interfaces gewährleisten, wird im Blogeintrag „Interfaces für besseres Unittesten“ anhand eines Beispiels erklärt.
- Ein Unittest testet immer genau eine Funktion.
- Die Eingabeparameter und das zu erzielende Ergebnis müssen bekannt sein, um die Funktion auf ihre Richtigkeit zu überprüfen.
- Für jede Randbedingung einer Funktion muss ein Unittest erstellt werden um alle möglichen Szenarien, für die diese Funktion verwendet werden kann, abzudecken.
Aufbau eines Unittests
Für das Erstellen von strukturierten und leserlichen Unittests gibt es ein paar Punkte zu beachten:
- Für jedes Szenario wird eine Testmethode verfasst.
- Der Name der Testmethode enthält welche Methode getestet wird und worauf sie getestet wird.
- Der Name der Testmethode endet mit dem Wort „Should“.
- Beispiel: die Test-Methode für die Funktion Calc(int n1, int n2) welche zwei Nummern addiert könnte Calc::AddTwoNumbersShould() benannt werden
- Ein Test kann in der Regel in drei Blöcke unterteilt werden
-
- Arrange:
– Vorbereitung der Input Parameter
– Festlegen des zu erzielenden Ergebnisses
– Erstellen von Objekten für den Test - Act:
– Ausführung der zu testenden Funktion
– Act besteht meistens nur aus einer einzigen Zeile - Assert:
– Überprüfen der Ergebnisse
– Abfragen ob Exceptiones geworfen wurden
- Arrange:
Diese Unterteilung lässt deine Tests leserlicher und verständlicher werden, da Sie anhand eines Blickes direkt wissen was die Inputparameter und zu erwarteten Ergebnisse sind (Arrange Block), was getestet wird (Act-Block) und weshalb der Test fehlschlägt (Assert-Block).
Beispiel zum Unittesten einer Methode für einen Taschenrechner
Die Folgenden Schritte werden beschrieben
- Setup
- Die zu testende Klasse: Calculator
- Wie legt man ein Test-Projekt an?
- Wie legt man eine Test-Klasse an?
- Hinzufügen des Verweises auf das zu testende Projekt
- Erstellen eines Tests
a. Was kann alles geprüft werden? - Ausführen eines Tests
a. mit dem Testexplorer
b. mit axocover
1. Setup
Im folgenden Beispiel wurde folgendes Setup verwendet
- Visual Studio 2019
- NUnitTest: VisualStudio -> Extras -> NuGet package
o NUnit v3.13.3
o NUnit3TestAdapter v.4.4. - Aoxcover: VisualStudio -> Extras -> Erweiterung und Updates
o AxoCover v 1.1.7.0
2. Die zu testende Klasse: Calculator
Für dieses Beispiel soll die Methode Calc(int n1, int n2) von der Klasse Calculator getestet werden, welche in Abbildung 1 zu sehen ist. Hierbei beschränken wir uns nur auf die Addition.
3. Test-Projekt anlegen
Legen Sie in der Projektmappe ein zweites Projekt vom Typ „NUnit 3 Unit Test Project“ an.
4. Erstellen einer Test-Klasse
Erstellen Sie nun in Ihrem neuen Test-Projekt eine neue Testklasse an. Wählen Sie dafür den Typ „NUnit Test Fixture“ aus und benennen Sie diese nach der Klasse oder Funktion, welche Sie testen möchten. Im Folgenden wurde der Name „CalcShould“ verwendet.
In der neu erstellten Test Fixture wird Ihnen automatisch eine erste Test-Methode TestMethod(), wie in Abbildung 4, erstellt.
5. Hinzufügen des Verweises auf das zu testende Projekt
Um nun auf die Klassen aus Ihrem eigentlichen Projekt zugreifen zu können, muss zunächst noch ein Verweis auf Ihr Projekt hinzufügen werden. Machen Sie dafür einen Rechtsklick auf die Verweise Ihres Test-Projekts und wählen Sie „Verweise hinzufügen“. Fügen Sie anschließend Ihr Projekt hinzu.
Nun können Sie eine Using-Direktive auf ihr Hauptprojekt in Ihre Test Fixture eintragen. Nun können sie auf Ihre zu testende Klasse zuzugreifen.
6. Erstellen eines Tests
In Abbildung 7 ist eine Testmethode abgebildet, welche Die Methode Calc der Klasse Calculator testet. Hierbei wurde nur die Addition von zwei natürlichen Zahlen getestet.
Arrange:
Im Arrange-Block wird ein Objekt der Klasse Calculator erstellt und die Input Parameter number1 und number2, sowie das zu erwartende Ergebnis resultShould festgelegt.
Act:
Im Act-Block wird die Methode Calc mit den entsprechenden Input Parametern ausgeführt und das Ergebnis in der Variable result gespeichert.
Assert:
Im Assert-Block wird überprüft, ob das Ergebnis result mit dem zu erwartenden Ergebnis resultShould übereinstimmt.
Was kann in einem Unittest geprüft werden?
Im Folgenden sind einige hilfreiche Möglichkeiten aufgelistet, um die Klasse Assert in Unittests zu verwenden.
Anwendung von Assert | Bedeutung |
---|---|
Assert.That() Is.Equal(var x, var y) | Test kann nur bestanden werden, wenn x den gleichen Wert hat wie y |
Assert.Fail() | Der Test kann nur bestanden werden, wenn der Code Failed bevor er diese Zeile erreicht. Z.B., weil vorher eine unbehandelte Exception geworfen wird |
Assert.That(x, Is.GreaterThan(y)); | Der Test kann nur bestanden werden wenn x größer ist als y |
Weitere Möglichkeiten um Asser. Zu nutzen:
https://docs.nunit.org/articles/nunit/writing-tests/assertions/assertions.html
7. Einen Unittest ausführen
Nun da unser Testprojekt erstellt und der erste Test implementiert ist, müssen wir diesen Test ausführen. Wir können dies mit dem Testexplorer von Visual Studio oder mit dem Package Axocover.
Tests ausführen mit dem Testexplorer
Visual Studio hat einen eigenen Testexplorer, um Tests auszuführen. Um diesen zu nutzen, klicken Sie auf Test -> Fenster -> Testexplorer. Es wird sich der Testexplorer öffnen und alle Tests in Ihrer Projektmappe anzeigen. Mit einem Klick auf „Alle Ausführen“ können Sie all Ihre Tests auf einmal ausführen. Bestandene Tests werden grün markiert.
Tests ausführen mit Axocover
Axocover erlaubt uns ebenfalls wie der Testexplorer einzelne oder mehrere Tests auf einmal durchzuführen und zeigt uns anschließend die Ergebnisse an
Es hat jedoch zusätzlich noch die Coverage Funktion. Anstatt die Tests normal auszuführen, können Tests auch „gecovert“ werden. Die ausgewählten Tests werden dann ausgeführt und alle Codezeilen, die für die Tests durchlaufen wurden, werden rechts von der Zeilennummerierung grün markiert.
Alle nicht durchlaufenen Zeilen werden rot markiert. In Abbildung 10 ist zu sehen das alle Zeilen des Tests durchlaufen wurden. Für Entwickler jedoch interessanter ist in Abbildung 11 zu sehen das nur die Zeilen unserer zu testenden Klasse Calculator grün markiert, die auch für den Test durchlaufen wurden. Dort wurde nur der Case der Addition grün markiert und kein weiterer.
Dieses feature ist sehr hilfreich, um Buges bei durchgefallen Testläufen zu lokalisieren oder um zu überprüfen, dass die Tests alle möglichen Szenarien einer Methode abdecken. Das Covern dauert jedoch wesentlich länger als ein normaler Testdurchlauf und sollte deswegen gezielt verwendet werden.
Fazit
NUnit ermöglicht uns Testprojekte und Test-Fixtures anzulegen, um einzelne Klassen oder Teile eines C#-Projekts zu testen. Zum Testen einer Funktion oder Klasse sollte immer ein Unittest pro Szenario erstellt werden, welcher in die drei Blöcke Arrange, Act und Assert eingeteilt sein sollte.
Im Assert-Block kann mithilfe der Klasse Assert überprüfet werden ob Ergebnisse übereinstimmen, ob Exceptions geworfen wurden und vieles mehr. Zu guter Letzt wurde gezeigt wie mithilfe von Axocover und der Coverage-Funktion überprüft werden kann welche Zeilen unseres Codes für einen Test durchlaufen wurden.