Freitag, 2. Juli 2010

Erstellen von Akzeptanztests mit einer DSL (Schritt 3 / Teil 2)

In diesem Post soll nun der Ablauf der Erstellung eines Akzeptanztests beschrieben werden. Dabei wird zuerst anhand des Use Case ein Modell entwickelt. Später wird ein allgemein geltender Ausgangszustand und zwei Testfälle basierend auf dem Modell entwickelt. Anhand dieser Testfälle werden dann die Möglichkeiten und Probleme diskutiert.

Dabei möchte ich hier den Use Case darstellen, den ich bereits im letzten Post vorgestellt habe. Dieser ist nicht sehr umfangreich und deshalb als Beispiel gut geeignet.

Projekt-Struktur des Eclipse-Plugins

Bevor wir zur eigentlichen Umsetzung kommen, möchte ich gerne auf einen sehr praxisbezogenen Punkt eingehen. In dem Prototyp, der gerade in Arbeit ist, habe ich mir eine relativ genau vorgegebene Struktur überlegt, welche das Eclipse-Plugin erzeugt. Dies soll dem Benutzer dabei helfen, Ordnung in seine Use Cases und Akzeptanztest-Fälle zu bringen:

EclipseProjectStruktur01

Dabei wird (über entsprechende Benutzerassistenten) eine Package-ähnliche Ordner-Struktur erzeugt, wie man sie aus Java kennt. Im Package versteckt sich jeweils der Name des Use Case und als Subpackages die Testfälle dieses Use Case. Hierbei muss man auf die Länge der Namen achten, da zu lange Namen nicht als Packages verwendet werden können. Dennoch sollten sie sinnvoll sein (anders als im Bild).

Im Use Case Package befindet sich die Ausgangszustands-Datei, gekennzeichnet durch das “init” im Namen. Alle Dateien mit der Endung “atdsl” sind DSL-Dateien. Die Packages der Test Cases haben jeweils eine Datei für den Anfangs- (before) und den Endzustand (after). (Die Namen wurden in Anlehnung an JUnit gewählt).

Die wichtigen Informationen einerseits für den Use Case, aber auch für die Testfälle befinden sich im jeweiligen Ordner in properties-Dateien. Auf deren Inhalt soll momentan nicht eingegangen werden, dies ist für den Ablauf zunächst nicht wichtig. Außerdem befindet sich das Ganze noch in Entwicklung. Mögliche Properties sind Pfade zu Modell und DSL-Dateien, aber auch zum Beispiel eine Prosa-Beschreibung des Use Case bzw. Testfalls.

Des Weiteren befinden sich alle Dateien, die vom Benutzer editiert werden können bzw. sollen in einem Source-Ordner “src”. Wie man im Bild sehen kann, gibt es einen zweiten Ordner “src-gen”. Dieser hält sich zur Verfügung, um bei der Code-Generierung, welche im Anschluss an die Formulierung der Tests folgen soll, erzeugte Quellcode-Dateien aufzunehmen, beispielsweise vom Ecore-Modell.

Das Domänen-Modell

Der Beispiel-Use Case beschäftigt sich mit der Domäne “Buchungssystem”. Dabei geht es um die Buchung eines Hotel-Zimmers durch einen Gast. Der Benutzer könnte zunächst ein vollständiges Domänen-Modell entwickeln, welches als Basis für die gesamte zu entwickelnde Software gilt. Wir beschreiten hier aber einen anderen Weg: das Domänen-Modell wird zunächst nur soweit entwickelt, wie es für den Use Case notwendig ist. Normalerweise kann so auch jeder Use Case sein eigenes Modell besitzen. Man könnte aber natürlich auch nur ein Modell verwenden und dies mit jedem Use Case erweitern. So oder so bietet diese Vorgehensweise den Vorteil, dass der Benutzer über seine Domäne reflektiert und nur die wirklich benötigten Elemente auswählt. Das Modell wird jeweils in einem iterativen Schritt weiterentwickelt.

Das in diesem Beispiel verwendete Domänen-Modell ist relativ einfach. Daher wird es hier auch keine große Iteration geben. Bei komplexeren Domänen und Use Cases sieht dies selbstverständlich anders aus.

Um den Use Case der Buchung formulieren zu können, benötigt man mindestens folgende Modell-Elemente:

EcoreDiagUsecase01

Es existieren die Entitäten Beherbergungsbetrieb, Zimmer, Gast und Buchung. Eine Buchung hat kann mehrere Zimmer und Gäste haben, ein Beherbergungsbetrieb hat mehrere Zimmer. Die Enumerationen für Betriebsart, Zimmerart usw. sind im Bild der Übersichtlichkeit halber nicht zu sehen.

In unserem Fall reicht dieses Modell schon für beide Testfälle aus.

Ausgangszustand

Der Ausgangszustand dient dazu, einen bestimmten Zustand in der Datenbank herzustellen, unabhängig davon, welcher Testfall gerade läuft. Um eine gewisse Testhygiene zu erreichen, wird er entweder vor oder nach einem Testfall wiederhergestellt. Dabei könnte der Ausgangszustand für die gesamte Domäne gültig sein oder auch nur für einen Use Case. Momentan habe ich dafür entschieden, den Ausgangszustand für jeden Use Case einzeln festzulegen.

Im Ausgangszustand können einige Objekte bereits angelegt werden. Hier beschränken wir uns darauf, sicherzustellen, dass die benötigten Tabellen leer sind. Deshalb werden alle Einträge darauf gelöscht, in dem alle Objekte gelöscht werden:

Import 'model/Hotel.ecore'

Lösche alle Beherbergungsbetrieb
Lösche alle Zimmer
Lösche alle Gast

Erster Testfall – Erfolgreiche Buchung eines Zimmers

Bei einem Buchungssystem für Hotels und andere Beherbergungsbetriebe gibt es eine Besonderheit. Der Gast kann sich meist sein Zimmer nicht genau aussuchen. Er gibt bestimmte Optionen an, wie das er ein Doppelzimmer haben möchte, und bekommt dann ein entsprechendes zugewiesen. Deshalb wird im Anfangszustand nur ein Hotel mit einem Zimmer angelegt.

Anfangszustand in der DSL:

Import '../model/Hotel.ecore'

Definiere Hotel = Beherbergungsbetrieb {
Betriebsart = Hotel
}

Erzeuge def:Hotel Hotel1 {
Bezeichnung = "Hotel am Park"
}
hat ZimmerListe: zimmer1

Erzeuge Zimmer zimmer1 {
Zimmerart = Einzelzimmer
Zimmernummer = "1"
}

Erzeuge Gast gast1 {
Vorname = "Max"
Nachname = "Mustermann"
Email = "Max@Mustermann.de"
}

Im Endzustand wird nach der angelegten Buchung gesucht. Wie bereits im vorherigen Post angedeutet, muss man sich hier darauf verlassen, dass das getestete Softwaremodul bestimmte Standardwerte verwendet, wie hier etwa die Anreise am 1.7. und die Abreise am 5.7., da es so nicht möglich ist, diese Werte als Input zu übergeben.

Endzustand in der DSL:

Import '../model/Hotel.ecore'
// benutze Objekte aus dem Anfangszustand
Import 'testcase1_before.atdsl'

Finde Buchung buchung1 {
Anreisedatum = 01.07.2010
Abreisedatum = 05.07.2010
Leistungsart = Vollpension
}
hat Zimmer: zimmer1
hat Gaeste: gast1

// Buchung muss vorhanden sein
Nicht Null? buchung1

Sollte die Buchung nicht gefunden werden, so ist die Variable am Ende null. Das heißt, es muss nun überprüft werden, ob die Buchung “Nicht null” ist. Ist das Ergebnis der Prüfung true, dann ist der Test erfolgreich.

Wie im Beispiel zu sehen ist, verwendet die DSL-Datei des Endzustands die des Anfangszustandes, um die da erzeugten Objekte zu übernehmen. Ein anderer Weg wäre, hier noch einmal explizit nach dem angelegten Zimmer und dem Gast zu suchen.

Zweiter Testfall – Doppelte Buchung

Im zweiten Testfall werden im Anfangszustand prinzipiell die selben Objekte erzeugt. Außerdem wird bereits eine Buchung des Zimmers durch einen zweiten Gast erzeugt. So sollte das Zimmer nicht mehr belegt werden können.

Anfangszustand:

Import '../model/Hotel.ecore'

Erzeuge Buchung buchung1 {
Anreisedatum = 01.07.2010
Abreisedatum = 05.07.2010
Leistungsart = Halbpension
}
hat Zimmer: zimmer1
hat Gaeste: gast2

Definiere Hotel = Beherbergungsbetrieb {
Betriebsart = Hotel
}

Erzeuge def:Hotel Hotel1 {
Bezeichnung = "Hotel am Park"
} hat ZimmerListe: zimmer1

Erzeuge Zimmer zimmer1 {
Zimmerart = Einzelzimmer
Zimmernummer = "1"
}

Erzeuge Gast gast1 {
Vorname = "Max"
Nachname = "Mustermann"
Email = "Max@Mustermann.de"
}

Erzeuge Gast gast2 {
Vorname = "Moritz"
Nachname = "Meier"
Email = "Moritz@Meier.de"
}

Wie man hier sieht, befindet sich die Buchung im selben Zeitraum wie die, die durch die zu testende Software angelegt werden soll. Im Endzustand wird nun zunächst nach der angelegten Buchung gesucht. Diese muss natürlich noch vorhanden sein und darf z.B. nicht überschrieben worden sein. Sucht man hingegen nach der Buchung, die eigentlich nun hätte angelegt werden sollen, so darf diese natürlich nicht vorhanden sein.

Endzustand:

Import '../model/Hotel.ecore'
// benutze Objekte aus dem Anfangszustand
Import 'testcase2_before.atdsl'

Finde Buchung buchung1 {
Anreisedatum = 01.07.2010
Abreisedatum = 05.07.2010
Leistungsart = Halbpension
}
hat Zimmer: zimmer1
hat Gaeste: gast2

// Buchung muss vorhanden sein
Nicht Null? buchung1

Finde Buchung buchung2 {
Anreisedatum = 01.07.2010
Abreisedatum = 05.07.2010
Leistungsart = Vollpension
}
hat Zimmer: zimmer1
hat Gaeste: gast1

// Buchung darf nicht vorganden sein
Null? buchung2

So wird nun abgefragt, ob die entsprechende Variable null ist.

Eine weitere Möglichkeit wäre, die Suche einzuschränken und beispielsweise nur nach einer Buchung im besagten Zeitraum und für dieses spezielle Zimmer zu suchen, also unabhängig vom Gast. Sollten mehrere Buchungen existieren, würde hier eine Liste zurückgegeben werden. (Wie die DSL mit Ergebnismengen, die mehr als ein Objekt beinhalten, umgehen muss, darüber werde ich einen eigenen Post schreiben.) Man könnte nun überprüfen, ob die Liste nun mehrere Buchungen beinhaltet oder nur die eine, die schon vorher angelegt wurde. Dies wäre ein anderer Weg, den Testfall zu überprüfen.

Bemerkungen zu diesem Beispiel

Wie man in den beiden Testfällen sehen konnte, so sind die im Anfangszustand erzeugten Objekte fast gleich. Da liegt die Idee nahe, einfach bereits im Ausgangszustand diese Objekte zu erzeugen. Dies kann man durchaus machen, abhängig davon, wie oft und in wievielen Testfällen die Objekte gleichermaßen verwendet werden. Wie bereits angedeutet spielt hier auch eine Rolle, ob man einen Ausgangszustand je Use Case definiert, oder ob es für das gesamte zu testende System einen einzigen Ausgangszustand gibt. Dazu kommt noch, dass man eventuell einen sauberen Zustand der Datenbank bewahren will und die Objekte nicht behalten möchte, obwohl sie in jedem Testfall verwendet werden.

Dieses Beispiel ist leider nicht so umfangreich, um alle Möglichkeiten zu zeigen. Eventuell werde ich in einem der nächsten Posts das Beispiel wieder aufgreifen und erweitern.

Desweiteren sollte dieses Beispiel die grundlegende Idee der Beschreibung von Akzeptanztests in einer DSL wiedergeben. Hier wird sicher noch einige Arbeit investiert werden, um die DSL noch besser und den Prozess und die Struktur des Projektes optimaler zu gestalten.

Keine Kommentare:

Kommentar veröffentlichen