Unit-Tests - NUnit und NMock
und testgetriebene Entwicklung
"Testen ist der Prozess, ein Programm mit der Absicht auszuführen, Fehler zu finden"
G.J. Myers 1979
Anwendungen:
Systematisches und regelmäßiges Testen ist ein integraler Bestandteil der Software-Qualitätssicherung.
Modultestsoftware wie NUnit dient zur Verifikation der Korrektheit von Methoden,
Klassen und Modulen. Dabei wird jede einzelne Methode mit einer geeigneten Menge
relevanter Eingabeparameter aufgerufen und der Rückgabewert (und gegebenenfalls
die Seiteneffekte) überprüft. Die Menge der Eingabeparameter entsteht aus der Softwarespezifikation
und wird so gewählt, dass jeder Kontrollpfad in der Methode abgedeckt ist.
NMock hilft dem Programmierer, Klassen und Methoden zu simulieren. Wenn also eine
zu programmierende Methode eine andere Klasse verwendet, die (noch) nicht zur Verfügung
steht, deren Verhalten aber durch klare Spezifikation des Interfaces bekannt ist,
dann kann dieses Verhalten mit einer "mock", also "Attrappe", der Klasse nachgebildet
werden. Jetzt kann die programmierte Methode mit Hilfe der Attrappenklasse getestet
werden. Der Vorteil dieses Verfahrens liegt auf der Hand. Würde man die Attrappenklasse
wie eine ganz normale Klasse selbst so implementieren, dass sie genau den vorgegebenen
Spezifikationen entspricht, dann würde man sie im Grunde vollständig implementieren.
Das Schreiben von geeigneten Tests geschieht gleichzeitig mit dem Programmieren
der zu testenden Methoden. Dadurch ändert sich das herkömmliche Programmiermodell
"Programmieren und danach prüfen, ob die Methode der Spezifikation genügt" zu: "Spezifikation
festlegen und danach programmieren, bis die Methode der Spezifikation genügt".
Diese Tests sind bei uns automatisiert. Damit kann nach Modifikationen und Erweiterungen
am Quelltext sofort festgestellt werden, ob die vorher erstellten Tests immer noch
dieselben Ergebnisse haben. Außerdem stellen sie sicher, dass die gewünschte Funktionalität
auch und gerade in Randbereichen erreicht wird.
NUnit bei komplexen Pocket-PC-Applikationen
Software-Qualitätssicherung durch Unit-Tests (Modul-Tests) mit NUnit für Windows
Mobile (PocketPC) im .NET Compact Framework.
Im Auftrag eines in Münster ansässigen Beratungshauses erstellten wir für einen
Betrieb für Beratungs- und Software-Lösungen im Bereich Telekommunikation, Transport
und Logistik in Köln so genannte Unit-Tests für Windows Mobile basierte Einheiten.
Diese Modultests, die zur Verifikation der Korrektheit der Module dienen, wurden
in diesem Falle speziell zur Überprüfung der Motorola Handhelds des weltweit größten
Express- und Logistikunternehmens durchgeführt. Dazu wurden Testmethoden und Verfahren
entwickelt, die teilweise Windows Mobile Software im .NET Compact Framework, Teil
des .NET Frameworks, der speziell für die Nutzung auf mobilen Endgeräten ausgerichtet
ist, nutzen. Es ermöglicht, Anwendungen für mobile Geräte zu schreiben oder sie auf
diese zu portieren (gegenüber des normalen .NET-Framework für den PC ist dieses
um eine größere Zahl von Klassen reduziert, die für intelligente Kleingeräte nicht
benötigt werden oder zu viel Speicherplatzbedarf produzieren würden). Das .NET Compact
Framework als eine Anwendung des .NET Frameworks - der Plattform für Programme, die mit unterstützenden
Programmiersprachen entwickelt wurde und eine Technologie darstellt, die verschiedene
Betriebssystem-Funktionen zusammenfasst und an einem zentralen Punkt anbietet -
stellt sich für uns als eine Softwareschicht dar, die sich zwischen der Anwendungs-
und der Betriebssystem-Schicht befindet. Hier bekommen wir die von dem Programm
benötigten Basis-Funktionen zur Verfügung gestellt und können die Anwendungsprogramme
plattformunabhängig ablaufen lassen. Dies geschieht gesondert von der Hardware und
dem Betriebssystem.
Mit Hilfe des Software-Framework NUnit und dem NMock-Framework konnten wir so die
Entwicklungsumgebung auf hundertprozentige Funktion testen, nicht zuletzt indem
wir eigene NMock-Objekte erstellten, die uns als Hilfsmittel für das automatisierte
Testen der Software dienten, da sie als Attrappen für reale Objekte fungieren, wenn
es zu aufwändig war, diese funktional nachzubilden.
NUnit in der Win32-Anwendungsentwicklung
Selbst einfach wirkende Programme sind "unter der Motorhaube" erstaunlich komplex.
Jede Aktion in der Benutzeroberfläche wird an geeignete Kontrollklassen weitergeleitet,
die ihrerseits dann Veränderungen am Datenmodell durchführen.
Das Testen der Integrität des Datenmodells ist eine relativ einfache Sache. Auch
das Abprüfen der einzelnen Aktionen aus den Kontrollklassen ist leicht durchführbar.
Damit ist es aber nicht getan, auch die Benutzereingaben müssen "simuliert" werden.
Im Bild sieht man die Durchführung einer Reihe von High Level Tests, welche nicht
die Funktionalität einzelner Methoden prüfen, sondern ihr Zusammenspiel bei einer
Benutzer- und Parametereingabe. Dabei wird jeweils eine "richtige" und eine "falsche"
Eingabe simuliert.
Dafür wird zunächst eine Instanz des entsprechenden Datenmodellobjektes erstellt.
Darauf wird dann, über Kontrollklassen, eine Instanz der diese Daten abbildenden
Oberflächencontrols verbunden.
Jetzt wird im Oberflächencontrol der Wert beispielsweise eines Textfeldes, eines
Auswahldialoges oder eines Häkchens gesetzt, genau so, wie das nach einer entsprechenden
Benutzereingabe der Fall wäre.
Das Oberflächencontrol sollte diese Eingabe über die Kontrollklassen an das Datenmodell
weiterleiten. Weiterhin sollte eine Prüfung der Datenintegrität erfolgen. Insbesondere
erwartet man jetzt Fehlermeldungen, wenn falsche "Eingaben" erfolgt oder logische
Randbedingungen der Daten verletzt worden sind. Schließlich erwartet man eine korrekte
Generierung von Ausgaben. In diesem Fall sind das Abschnitte einer Konfigurationsdatei,
die aus den Daten erstellt werden.
Alle diese Erwartungen werden jetzt systematisch abgeprüft: Ist die Eingabe in das
Datenmodell übernommen worden? Ist eine Fehlermeldung erzeugt worden, und ist eine
entsprechende Änderung in der Oberfläche erfolgt? Sind Konfigurationsabschnitte
erzeugt, enthalten sie die eingegebenen Daten - genügen sie der Spezifikation?
Ebenfalls im Bild zu sehen ist der High Level Test in umgekehrter Reihenfolge. Die
gleichen Klassen werden erzeugt, aber diesmal beginnt man nicht mit einer simulierten
Benutzereingabe, sondern mit einem simulierten Konfigurationsabschnitt, der vom
Parser an das Datenmodell weitergereicht wird. Geprüft wird jetzt, ob diese Werte
richtig im Oberflächencontrol angekommen sind und ob, im Falle einer "falschen"
Konfiguration, die entsprechenden Fehlermeldungen erzeugt werden.
Insgesamt wird also die gesamte Kontrollkette der Anwendung "von oben" in die Richtung
Benutzereingabe nach Konfiguration (im Bild: schreiben) und "von unten", also die
Richtung Konfiguration nach Benutzeroberfläche (im Bild: lesen) geprüft und zwar
für jeden einzelnen Parameter, jeweils mit einem richtigen (im Bild: positiv) und
einem falschen (im Bild: negativ) Parameterwert.
NUnit zum Testen von Schnittstellen und Treiberdefinitionen
Einige Geräte der Sicherheitstechnik durchlaufen, bevor sie in den Handel kommen,
eine Funktionsprüfung nach VdS (die Organisation „Vertrauen durch Sicherheit“) bzw.
EN. Dabei müssen die Geräte folgende Anforderungen erfüllen:
- Prüfung nach VdS 2110 B1b und B2b
- Prüfung nach VdS 2326 Pkt. 8.1.3 und 8.1.4
- Prüfung nach EN 50131-2-X
Für diese Funktionsprüfungen haben wir eine kundenspezifische Prüfstandsoftware
entwickelt, die unter anderem folgende Features hat:
- Durchführung der Prüfungen nach Anforderungen VdS und EN. Dies beinhaltet eine Prüfung
der Alarm- bzw. Störungsausgänge bei einem Betriebsspannungssprung, in einem bestimmten
Betriebsspannungsbereich und bei einer bestimmten Welligkeit der Betriebsspannung, sowie
die Abfallzeit des Alarm- bzw. Störungsausgangs des zu überprüfenden Geräts.
- Abbildung des Workflows, wie er in den oben genannten Normen vorgeschrieben ist.
Der Prüfer wird von unserer Software durch den Prüfungsverlauf geführt.
- Frei parametrierbare Spannungskurvenverläufe: Es sind unter anderen folgende Parameter
einstellbar:
1. maximale bzw. minimale Spannungen der Spannungskurven
2. Umschaltzeiten
3. Zeitintervalle (Wie lange wird ein Spannungszustand gehalten)
4. Wiederholungsangaben
5. Spannungsanstiegs- und Spannungsabstiegsgeschwindigkeiten (Flankensteilheit)
6. Amplituden
7. Frequenzen
- Die Parameter der Spannungskurvenverläufe sowie die Messwerte werden in einer MS-SQL-Datenbank
abgelegt. Dies ist eine sichere Investition, da diese Datenbank von vielen Programmen
unterstützt wird. Durch die Abspeicherung der Messwerte ist es möglich, frühere Messungen/Prüfungen
aufzurufen.
- Zur grafischen Auswertung der gemessenen Daten werden diese nach Microsoft Excel
übertragen. Dort werden dann mit den Messwerten automatisch Diagramme erstellt und
zur Anzeige gebracht. Diese können dann in Excel ausgedruckt werden. Die Diagramme
werden für alle getesteten Prüflinge erstellt.
Die Spannungsverläufe des Prüfstands werden wie in folgender Abbildung dargestellt,
von einem arbiträren Netzteil erzeugt, mit dem über eine GPIB-Schnittstelle nach
IEEE 488.2 kommuniziert wird. Auf dieser Schnittstelle wurde der jeweilige Befehlssatz
der Geräte aufgesetzt und diese Interface-Klassen mit Unit Tests vollständig auf
ihre korrekte Funktionsweise getestet.
Bei einer so klaren und definierten Trennung zwischen dem Interface für den Befehlssatz
und der Applikation sind Unit Tests besonders effektiv. Schon vor der Implementierung
der Anwendung selbst kann die "Treiberschicht", in diesem Fall für den GPIB-Bus,
vollständig geprüft werden - treten Probleme während der Entwicklung der Applikation
auf, lassen sich diese ausschließlich auf die Anwendung selbst zurückführen.
Für einen vollständigen Unit Test ist die Prüfung auf jede einzelne mögliche Verzweigung,
also alle Pfade im Ablauf des Quellcodes, Grundvoraussetzung. Daher wurden für jeden
einzelnen Befehl der Kommunikationsschicht Testfunktionen geschrieben, die alle
möglichen Fehlerfälle der Funktion simulieren und die dessen Verarbeitung prüfen.