Freitag, 9. Juli 2010

Umgang mit DB-Abfragen in der DSL

Einführung

Bevor die Schrittfolge zur Erstellung von Akzeptanztests weitergeführt wird, möchte ich an dieser Stelle gerne noch eine Problemstellung einschieben, welche mir vor kurzem aufgefallen ist. Es handelt sich grob gesagt um das Problem, dass der Benutzer der DSL nicht weiß, ob eine Abfrage der Datenbank (meist im Endzustand, z.B. “Finde Gast…”) genau ein Objekt zurückgibt, oder mehrere. Dies kann man gut mit einem SELECT-Befehl in SQL vergleichen. Prinzipiell gibt dieser ein Resultset mit beliebig vielen Zeilen zurück. Ebenso kann man dies auf die DSL übertragen.

Das bedeutet nun, dass:

  1. die DSL geeignete Sprachmittel zur Verfügung stellen muss, um eventuell zu überprüfen, wie groß die Ergebnismenge ist
  2. der Benutzer sich konkret damit auseinandersetzen muss, wie viele Objekte er als Ergebnis seiner Abfrage erwartet

Letzteres könnte man als Bestandteil der Fachmodellierung sehen, den der Benutzer sowieso betrachten muss. Insofern dürfte er keine Schwierigkeiten haben, die Anzahl der Elemente in der Ergebnismenge zu bestimmen.

Am besten verfolgt man das Ganze an Hand eines Beispiels:

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

Nicht Null? buchung1

Die ist das Beispiel, welches ich bereits verwendet hatte, um die Formulierung der Akzeptanztests zu erläutern. In dem Beispiel bin ich davon ausgegangen, dass es nur eine Buchung geben kann, was in diesem Fall auch selbstverständlich sein sollte.

Jedoch könnte es passieren, dass man beispielsweise durch ein fehlerhaftes Programm mehrere Buchungen zurück bekommt, oder dies absichtlich durch ein Einschränken der Abfrage (z.B. indem man die Referenz auf Zimmer und Gast entfernt) hervorruft. Wie soll sich die DSL nun verhalten?

Verschiedene Assertions für Objekte und Listen

Mein erster Vorschlag ist, die Ergebnismenge in der DSL erst einmal gleich zu behandelt. Das heißt, die Variable buchung1 von oben kann ein einzelnes Objekt sein oder eine Liste von Buchungs-Objekten.

Nun kommt es auf die Assertion an, die man durchführen möchte. Es sollte sowohl Assertions für einzelne Objekte oder für Listen geben. Dabei gibt es für einzelne Objekte:

  • Nicht Null
  • Null
  • Gleich
  • Ungleich

Diese können nur verwendet werden, wenn es tatsächlich nur ein Objekt gibt, ansonsten schlägt der Testcase fehl. Das bedeutet auch, dass der Benutzer, wenn er einen Test schreibt, diese Assertions prinzipiell nur verwendet, wenn er der Meinung ist, dass hier nur ein Objekt zurückgegeben wird. Dies ist somit im gewissen Sinne auch Teil der Fachmodellierung.

Für Listen hingegen könnten folgende Assertions verwendet werden:

  • Beinhaltet
  • Beinhaltet nicht

Diese beiden sind je das Äquivalent zu Gleich und Ungleich. Sie überprüfen, ob ein bestimmtes Objekt in der Ergebnismenge vorhanden ist oder nicht. Hier ist noch zu sagen, dass eine Liste auch nur ein Objekt beinhalten kann, wobei sich das dann mit obigen Fall überschneidet. Das heißt, man kann prinzipiell immer annehmen, dass eine Liste von Objekten zurückgegeben wird und auf die entsprechenden Assertions zurückgreifen. Die Assertions für nur ein Objekt sind dann nur dazu da, zu erzwingen, dass es überhaupt nur ein Objekt geben darf.

Übrigens würde das System hinter der DSL immer mit Listen arbeiten. Für die Assertions Nicht Null, Null usw. würde dann immer zuerst geprüft werden, ob die Liste nur ein Element beinhaltet (oder leer ist).

Assertions für Größe der Ergebnismenge

Zusätzlich könnte die DSL direkte Assertions anbieten, die den Zustand der Ergebnismenge überprüfen, wie:

  • GenauEinElement?
  • MehrereElemente?
  • Leer?

Ich denke, die Begriffe sind selbsterklärend. Eventuell könnten die Namen ein bisschen eleganter ausfallen. Das Beispiel von oben könnte man nun folgendermaßen erweitern:

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

GenauEinElement? buchung1

// ist jetzt eigentlich überflüssig
Nicht Null? buchung1

Dadurch, dass man nun prüft, ob die Ergebnismenge genau ein Element bzw. Objekt beinhaltet, ist die Nicht Null Anfrage eigentlich überflüssig. Hier müsste man nun evaluieren, wenn man diese Assertions einsetzt, ob man dann noch die speziell auf ein Element angepassten Assertions wie Null, Gleich usw. benötigt. Im Prinzip könnte man dann mit den Listen-Assertions arbeiten.

Dieses Beispiel soll nun noch einmal die Vergleichsvariante zeigen, die ich bis jetzt ein bisschen vernachlässigt habe:

Finde Buchung buchung1 {
Anreisedatum = 01.07.2010
Abreisedatum = 05.07.2010
}
hat Zimmer: zimmer1

// ist nur ein Objekt zurückgekommen (Zimmer nur einmal gebucht)
GenauEinElement? buchung1

// das Ergebnis muss genau diese Buchung beinhalten
Beinhaltet? buchung1 vergleichsBuchung
// oder alternativ:
Gleich? buchung1 vergleichsBuchung

// diese Buchung muss es sein
Erzeuge Buchung vergleichsBuchung {
Anreisedatum = 01.07.2010
Abreisedatum = 05.07.2010
Leistungsart = Vollpension
}
hat Zimmer: zimmer1
hat Gaeste: gast1

Hier sind nun mehrere Dinge zu beachten. Einerseits macht ein Vergleich nur Sinn, wenn man die Einschränkungen der Eigenschaften und Referenzen bei der Abfrage (Finde) verringert. Denn wenn man alle Eigenschaften belegt, also gleich nach dem Objekt sucht, was man haben möchte, so reicht ein einfaches Nicht Null um zu testen, ob das Objekt in der Ergebnismenge existiert.

Eine andere Sache ist, dass man hier gleichermaßen Beinhaltet und Gleich anwenden kann, da man vorher genau überprüft hat, dass es nur ein Element in der Ergebnismenge geben darf. Dies ist auch sinnvoll, denn man hat ja schließlich nach der Buchung für ein bestimmtes Zimmer gefragt und diese darf für den angegebenen Zeitraum nur einmal existieren.

Wichtig ist noch, dass das Vergleichsobjekt hier mit Erzeuge definiert wird. Da wir uns hier in einem Endzustand eines Testfalls befinden, wird das Objekt selbstverständlich nicht mehr in der Datenbank erzeugt. Aber um sich solch eine Möglichkeit nicht zu versperren, könnte man in diesem Fall darüber nachdenken, die DSL so zu definieren, dass Objekte, die nur zur Laufzeit benötigt werden, erstellt werden, indem man das Erzeuge weglässt.

Ich bitte noch zu beachten, dass dieses Beispiel nur dazu dient, die Möglichkeiten zu zeigen. Das ursprüngliche Beispiel von oben ist deutlich kürzer und deshalb vorzuziehen.

Schlussbemerkung

Ich denke, dass diese Lösung mit den verschiedenen Assertions durchaus relativ intuitiv zu benutzen ist. Jedoch könnte man die Assertions, welche extra für einzelne Objekte vorhanden sind, weglassen und stattdessen immer mit Listen arbeiten. Schließlich kann eine Ergebnismenge ja auch eine Liste mit nur einem Element sein. Durch die Assertions zum Überprüfen der Listengrößen hat man wieder die volle Funktionalität.

Momentan bin ich dabei, diese Dinge im Prototyp umzusetzen. Ich möchte in der nächsten Zeit erst einmal soweit kommen, dass ich mit dem in den letzten Posts gezeigten Beispiel den Hibernate-Code generieren kann, der dann die Objekte aus der DSL letztendlich in die Datenbank bringt bzw. wieder abfragt. Wenn die grundlegenden Sachen umgesetzt sind, werde ich einen Post über Schritt 4, die Codegenerierung veröffentlichen.

Keine Kommentare:

Kommentar veröffentlichen