Desktop und einfache Client-Server-Applikationen

Unit TestsUnit-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.

NUnit Test

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.

Toellner Spannungsbereich

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.