Dienstag, 29. März 2011

Die Beispieldomäne Forum

Um die Anforderungen an eine DSL für die Beschreibung von Akzeptanztests besser erfassen zu können, ist eine Beispieldomäne notwendig. Sebastian Teichgräber hat dazu eine Domäne aus der Beherbergungsbranche verwendet. Ich verwende als Beispieldomäne ein Online-Forum. Foren sind den meisten Internetbenutzern bekannt. Somit sind Anwendungsfälle in dieser Domäne leicht nachvollziehbar. Ein weiterer Vorteil ist, dass funktionierende und kostenlos verfügbare Forensoftware mit einigermaßen komplexen Datenbankstrukturen bereits existiert. Diese kann man verwenden, um die entwickelte DSL zu testen.

Online-Forum SMF


Ich verwende die Software Simple Machines Forum (kurz SMF) in Version 2.0 RC5. Bei der Installation wurde smf_ als Präfix für Tabellennamen angegeben. Somit heben sich die Tabellennamen besser von den Namen der Domänenobjekten ab, die manuell definiert werden. Die Software erzeugt bei der Erstellung 63 Tabellen. Somit macht es auch Sinn, von der Datenbankstruktur hin zum fachlichen Domänenmodell zu abstrahieren.

Anwendungsfälle in einem Online-Forum


Ein Online-Forum hat u.A. die folgenden Anwendungsfälle:



Der Anwendungsfall "Thema starten" sieht im Detail folgendermaßen aus:

Name Thema starten
Beschreibung Der Benutzer startet ein neues Thema.
Auslöser Der Benutzer möchte ein neues Thema starten.
Vorbedingungen
  • Der Benutzer ist angemeldet.
  • Das Unterforum, in dem das Thema erstellt werden soll, existiert.
  • Das zu erstellende Thema existiert in dem Unterforum noch nicht, in dem es erstellt werden soll.
Nachbedingung
  • Das zu erstellende Thema existiert im passenden Unterforum.
  • Das zu erstellende Thema hat einen Beitrag.
Standardablauf
  1. Der Benutzer wählt das Unterforum, in dem er ein Thema erstellen möchte.
  2. Der Benutzer wählt "Neues Thema".
  3. Der Benutzer gibt den Titel und den Text des Themas ein.
  4. Der Benutzer klickt auf senden.

Groovy-DSL für die Vor- und Nachbedingungen


Eine DSL für die Formulierung von Vor- und Nachbedingungen sollte so gestaltet sein, dass sie der natürlichen Sprache, die im letzten Abschnitt zur Formulierung verwendet wurde, möglichst ähnlich ist. Die Vor- und Nachbedingungen aus dem letzten Abschnitt können folgendermaßen als gültiger Groovy-Code dargestellt werden:

//Vorbedingungen
prüfe {
    Testbenutzer.istAngemeldet
    Testforum.existiert
    NeuesThema.existiertNicht
}

//Nachbedingungen
prüfe {
    NeuesThema.existiert
    Testforum.Themen.enthält NeuesThema
    NeuesThema.Beiträge.anzahl = 1
    NeuesThema.Beiträge.enthält NeuerBeitrag
}

Definition von Testdaten


Die Begriffe existiert, existiertNicht, anzahl und enthält sind eingebaute Bestandteile der DSL, da sie für die meisten Vor- und Nachbedingungen gebraucht werden. Die Begriffe Testbenutzer, Testforum, NeuesThema, NeuerBeitrag und istAngemeldet müssen vom Benutzer definiert werden, da sie domänenspezifisch sind. Die Definitionen sehen folgendermaßen aus:

definiere Testbenutzer {
    istEin Benutzer
    mit {
        Nickname = "Testbenutzer"
    }
}

definiere Testforum {
    istEin Forum
    mit {
        Name = "Testforum"
    }
}

definiere NeuesThema {
    istEin Thema
    mit {
        Titel = "Testthema"
    }
}

definiere NeuerBeitrag {
    istEin Beitrag
    mit {
        Titel = "Testthema"
        Text = "Testtext"
    }
}

Das Schlüsselwort definiere erzeugt ein neues Domänenobjekt in der Domäne. Mit istEin legt man fest auf welchen Domänenobjekt das zu erstellende basieren soll. Dabei werden alle Eigenschaften des Basisobjektes vererbt und sind im neuen verfügbar. Mit dem Schlüsselwort mit kann man Constraints für das neue Domainobjekt festlegen. Im Beispiel NeuerBeitrag wird z.B. festgelegt, dass der Titel "Testthema" und der Text "Testtext" sein muss.

Definition von Domänenobjekten


Was Benutzer, Forum, Thema und Beitrag sind muss ebenfalls definiert werden. Das ist je nach verwendeter Foren-Software anders. Bei dem verwendeten SMF-Forum kann man einen Beitrag beispielsweise folgendermaßen definieren:

definiere Beitrag {
    istEin smf_messages
    definiereEigenschaft Titel {
        istEigenschaft smf_messages.subject
    }
    definiereEigenschaft Text {
        istEigenschaft smf_messages.body
    }
}

Mit dem Schlüsselwort definiereEigenschaft legt man neue Eigenschaften für das Domänenobjekt fest. Diese können später z.B. in Constraints verwendet werden. In den meisten Fällen basiert die neue Eigenschaft auf vererbten Eigenschaft des Basisobjektes. Das wird durch das Schlüsselwort istEigenschaft festgelegt.

smf_messages ist eine Datenbanktabelle. subject und body sind Spalten dieser Tabelle. In der zu erstellenden Software ist smf_messages ebenfalls ein Domänenobjekt. Dieses Objekt wird von Software automatisch erstellt. Die Software erstellt für jede Tabelle mit all ihren Spalten jeweils genau ein Domänenobjekt. Diese Domänenobjekte bilden die Basis für alle benutzerdefinierten Domänenobjekte. Dadurch wird es möglich SQL-Queries zu generieren und die Vor- und Nachbedingungen überprüfen zu lassen.

Dienstag, 15. März 2011

Wie die Erstellung von Akzeptanztests in der Software ablaufen soll

Mit der fertigen Software soll es später mögliche sein, Akzeptanztests für datenbankbasierte Anwendungen zu erstellen und zu verwalten. Das Erstellen von Akzeptanztests soll dabei mithilfe von DSLs geschehen. Die Erstellung besteht aus den folgenden Schritten:

  • Beschreibung der Domäne
  • Beschreibung des Ausgangszustandes
  • Beschreibung wie die zu testende Software ausgeführt wird
  • Beschreibung des Endzustandes

Beschreibung der Domäne

In der Software soll es möglich sein, ein Domänenmodell zu beschreiben. Dieses Modell besteht aus Domänenobjekten. Die Domänenobjekte können z.B. Geschäftsobjekte, wie "Kunde" oder "Rechnung" sein. Die Domänenobjekte können dann verwendet werden, um den Ausgangs- und den Endzustand bei Testausführung zu beschreiben. In dem Domänenmodell wird beschrieben, welche Beziehung ein Domänenobjekt zu den Tabellen und Tabellenspalten der Datenbank hat. Dadurch wird es für die Software möglich, die Zustandsbeschreibungen mit Fachbegriffen aus der Domain in SQL-Queries zu übersetzen, diese auszuführen und das Ergebnis zurück zu liefern.

Um nicht bei null mit der Beschreibung des Domänenmodells anfangen zu müssen, kann dieses zu Beginn aus einer bereits vorhandenen Datenbank generiert werden. Dabei entspricht eine Tabelle der Datenbank mit all ihren Spalten genau einem Domänenobjekt. Dieses generierte Domänenmodell kann anschließend weiter bearbeitet werden, um es z.B. von technischen Details zu befreien (z.B. von "id"-Spalten).

Zum Anzeigen und Bearbeiten des Domänenmodells eignet sich wahrscheinlich eine grafische DSL am besten. Gerade bei großen Domänen mit sehr vielen Domänenobjekten ist es wahrscheinlich schwer den Überblick zu bewahren, wenn diese in einer textuellen DSL beschrieben wäre. Diese grafische DSL könnte sich vom Erscheinungsbild her an UML-Klassendiagrammen orientieren.

Beschreibung des Ausgangs- und Endzustandes

Bei der Beschreibung des Ausgangs- und des Endzustandes werden konkrete Instanzen einzelner Domänenobjekte beschrieben (z.B. Instanz "x" ist ein Domänenobjekt "Kunde" mit den Werten "Name" = "Mustermann" und "Vorname" = "Max"). In der Regel müssen nur wenige Instanzen beschrieben werden. Dadurch bleibt der Überblick auch bei der Beschreibung durch eine textuelle DSL erhalten. Im Idealfall können bei einer textuellen DSL die Vor- und Nachbedingungen aus den Use-Cases möglichst ähnlich übernommen werden.

Für die Beschreibung des Ausgangs- und des Endzustandes kann die gleiche DSL verwendet werden. Diese DSL hätte die grundlegende Form "Es existiert 20 von X". Bei der Auswertung der Beschreibung des Endzustandes wird überprüft, ob diese Aussage wahr ist. Ist sie es, so war der Test erfolgreich.
Bei der Auswertung der Beschreibung des Ausgangszustandes wird ebenfalls überprüft ob die Aussage wahr ist. Ist sie es, so kann mit der Ausführung der zu testenden Software begonnen werden. Ist sie es nicht, so kann von der Software angeboten werden den Ausgangszustand herzustellen.

Beschreibung wie die Software ausgeführt wird

Um den Akzeptanztest durchführen zu können, muss angegeben werden, wie die zu testende Software auszuführen ist. Hierfür ist wohl ein grafischer Dialog am besten geeignet. Dieser Dialog könnte verschiedene Möglichkeiten zur Ausführung der Software bieten, z.B.:

  • Ausführung von Javacode: So hat man z.B. die Möglichkeit eine zu testende Klasse zu instanziieren und auszuführen.
  • Ausführung von ausführbaren Dateien und Jar-Archiven mit Angabe von Paramatern
  • Manuelle Ausführung: So kann man z.B. eine Aktion über das Webinterface der Software per Hand ausführen.

Mittwoch, 9. März 2011

Interne DSL mit Groovy Teil 2

Im ersten Teil habe ich Techniken vorgestellt, mit denen man interne DSLs in Groovy erstellen kann. Im zweiten Teil werde ich zeigen, wie die Fragebogen-DSL aus dem Clojure Beitrag in Groovy aussehen könnte.

Fragebogengenerator als API


Man könnte den Fragebogengenerator mit einer API zur Verfügung stellen.

Klassendiagramm des Fragebogengenerators

In Groovy implementiert könnte das folgendermaßen aussehen.

enum Fragetyp {
    EINFACHAUSWAHL("radio"),
    MEHRFACHAUSWAHL("checkbox"),
    TEXTEINGABE("text")
    
    String html
    
    Fragetyp(html) {
        this.html = html
    }
}

class Frage {
    String frage
    Fragetyp typ
    List<String> antworten = []
    
    void antwortHinzufügen(String antwort) {
        antworten << antwort
    }
    
    String antwortZuHtml(String type, String name, String value) {
            String html = "<input type=\"" + type + "\" name=\"" + name + "\""
            if(value != null)
                html += "value=\"" + value + "\">" + value
            else
                html += "/>" 
            html += "<br /‍>"
    }
    
    String zuHtml() {
        String html = "<h2>" + frage + "</h2>"
        if(typ == Fragetyp.TEXTEINGABE)
            html += antwortZuHtml(typ.html, frage, null)
        else
            antworten.each { antwort -> 
                html += antwortZuHtml(typ.html, frage, antwort)    
            }
        return html
    }
}

class Fragebogen {
    String title
    List<Frage> fragen = []
    
    void frageHinzufügen(Frage frage) {
        fragen << frage
    }
    
    String zuHtml() {
        String html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
        html += "<html><head><title>" + title + "</title></head><body><h1>" + title + "</h1><form action=\"\">"
        fragen.each { frage->
            html += frage.zuHtml()
        }
        html += "<br /‍><input type=\"submit\" value=\"Senden\" /></form></body></html>"
    }
}

Möchte man die API verwenden um einen Fragebogen zu erzeugen, sieht das folgendermaßen aus:

fragebogen = new Fragebogen(title:"Kfz-Umfrage")

frage = new Frage(typ:Fragetyp.EINFACHAUSWAHL, frage:  "Welche Marke hat ihr Fahrzeug?")
frage.antwortHinzufügen("Opel")
frage.antwortHinzufügen("Renault")
frage.antwortHinzufügen("BMW")
fragebogen.frageHinzufügen(frage)

frage = new Frage(typ:Fragetyp.TEXTEINGABE, frage: "Wie lautet die Modelbezeichnung?")
fragebogen.frageHinzufügen(frage)

frage = new Frage(typ:Fragetyp.MEHRFACHAUSWAHL, frage:  "Welche Ausstattungsmerkmale hat ihr Fahrzeug?")
frage.antwortHinzufügen("Klimaanlage")
frage.antwortHinzufügen("Navigationssystem")
frage.antwortHinzufügen("ESP")
fragebogen.frageHinzufügen(frage)

println(fragebogen.zuHtml())

Eine General Purpose Language wie Groovy oder Java muss Problemstellungen aus vielen unterschiedlichen Domains abbilden können. So eine Sprache muss sehr flexibel sein. Dadurch ist viel technischer Overhead nötig um ein Problem aus der Domain "Fragebogen erstellen" zu beschreiben. Die Flexibilität von Groovy zeigt sich in diesem Beispiel daran, dass man z.B. mehrere Fragebögen auf einmal erstellen könnte. Anschließend könnte man Fragen erstellen und diese zu verschiedenen Fragebögen zuweisen.

kfzFragebogen = new Fragebogen(title:"Kfz-Umfrage")
handyFragebogen = new Fragebogen(title:"Handyfragebogen")

frage = new Frage(typ:Fragetyp.EINFACHAUSWAHL, frage:  "Welche Marke hat ihr Fahrzeug?")
frage.antwortHinzufügen("Opel")
frage.antwortHinzufügen("Renault")
frage.antwortHinzufügen("BMW")
kfzFragebogen.frageHinzufügen(frage)

frage = new Frage(typ:Fragetyp.MEHRFACHAUSWAHL, frage:  "Was für Betriebssystem läuft auf ihrem Handy?")
frage.antwortHinzufügen("Android")
frage.antwortHinzufügen("iOS")
frage.antwortHinzufügen("Windows Phone 7")
handyFragebogen.frageHinzufügen(frage)

println(kfzFragebogen.zuHtml())
println(handyFragebogen.zuHtml())

Fragebogengenerator als DSL


Das macht in der Domäne "Fragebogen erstellen" nicht viel Sinn. Diese Flexibilität ist hier nicht unbedingt nötig. Legt man z.B. fest, dass eine erstellte Frage automatisch zu dem zuletzt erstellten Fragebogen gehört, lässt sich viel technischer Overhead sparen. Die eingebüßte Flexibilität fällt nicht auf, da man es in den meisten Fällen ohnehin so gemacht hätte. Eine Beschreibung des Kfz-Fragebogen in einer internen DSL für diese Domain könnte dann folgendermaßen aussehen:

fragebogen "Kfz-Umfrage"
  frage einfachauswahl, "Welche Marke hat ihr Fahrzeug?"
    antwort "Opel"
    antwort "Renault"
    antwort "BMW"
  frage texteingabe, "Wie lautet die Modelbezeichnung?"
  frage mehrfachauswahl, "Welche Ausstattungsmerkmale hat ihr Fahrzeug?"
    antwort "Klimaanlage"
    antwort "Navigationssystem"
    antwort "ESP"

In dieser Repräsentation ist fast kein technischer Overhead mehr vorhanden. Somit kann auch ein Nicht-Programmierer erkennen um welche Domäne es hier geht. Nach kurzer Einarbeitungszeit kann der Nicht-Programmierer auch eigene Fragebögen erstellen oder vorhandene bearbeiten.

Damit das so funktioniert sind ein paar Wrapper-Funktionen für die weiter oben beschriebene API nötig.

aktuellerFragebogen = null
aktuelleFrage = null

void fragebogen(String title) {
    aktuellerFragebogen = new Fragebogen(title: title)
}

void frage(Fragetyp typ, String frage) {
    aktuelleFrage = new Frage(frage:frage, typ:typ)
    aktuellerFragebogen.frageHinzufügen(aktuelleFrage)
}

void antwort(String antwort) {
    aktuelleFrage.antwortHinzufügen(antwort)
}

einfachauswahl = Fragetyp.EINFACHAUSWAHL
mehrfachauswahl = Fragetyp.MEHRFACHAUSWAHL
texteingabe = Fragetyp.TEXTEINGABE
binding['fragebogen'] = this.&fragebogen
binding['frage'] = this.&frage
binding['antwort'] = this.&antwort

Fragebogengenerator als Interpreter


Mit dem folgenden Code wird es schließlich möglich die Fragebogen-DSL als Interpreter zu starten. Dieser nimmt einen Pfad zu einem Fragebogen entgegen, führt diesen aus und gibt schließlich den Fragebogen als HTML aus.

void ausführungAbbrechen () {
 println("Bitte geben Sie den Pfad zum Fragebogen als Parameter an.")
 System.exit(1)
}

if(this.args.length != 1)
 ausführungAbbrechen()
File eingabe = new File(args[0])
if(!eingabe.exists())
 ausführungAbbrechen()
new GroovyShell(binding).evaluate(eingabe)
    
println aktuellerFragebogen.zuHtml()

Das ganze kann man jetzt direkt ausführen,

groovy FragebogenDsl.groovy

oder kompilieren und eine runnable-Jar erstellen,

mkdir interpreter
cd interpreter
groovyc -d . X:\Pfad\zu\FragebogenDsl.groovy
jar -xf X:\Pfad\zu\Groovy\embeddable\groovy-all-*.jar
jar cvef FragebogenDsl Fragebogengenerator.jar *

welche man im Anschluss wie üblich ausführen kann.

java -jar Fragebogengenerator.jar

Links

Dienstag, 8. März 2011

Interne DSL mit Groovy Teil 1

Die zweite Sprache, die ich auf Eignung für interne DSLs testen will, ist Groovy. Groovy ist eine dynamische Sprache. Sie läuft ebenfalls in der Java VM. Die Syntax ist sehr ähnlich zu Java und damit wesentlich umfangreicher als die Syntax von Clojure. Dieses mehr an Syntax bedeutet gleichzeitig, dass mehr Techniken nötig sind um die Hostsprache Groovy so zu verbiegen, dass sie das "Look&Feel" einer DSL hat. In diesem ersten Teil werde ich einige Techniken von Groovy vorstellen, die zur Erstellung von internen DSLs genutzt werden können. Im zweiten Teil werde ich dann die Fragebogen DSL aus dem Clojure Beitrag mit Groovy umsetzen.

Techniken für die Erstellung von DSLs

Einsparung von Semikolons und Klammern

In Groovy sind Semikolons in den meisten Fällen optional. Klammern bei Funktionsaufrufen können auf der obersten Ebene ebenfalls entfallen.

//In Java und Groovy gültig
println("Test");
println("Hello"); println("World");
println(Integer.parseInt("3"));

println "Test"                                  //funktioniert
println "Hello" println "World"    //funktioniert nicht
println "Hello"; println "World"  //funktioniert
println Integer.parseInt "3"      //funktioniert nicht
println Integer.parseInt("3")     //funktioniert


Neue Methoden und Eigenschaften für Klassen zur Laufzeit hinzufügen

In Groovy ist es möglich jeder existierenden Klasse neue Methoden und Eigenschaften zur Laufzeit hinzuzufügen, ohne die Klassen neu kompilieren zu müssen. Dazu wird der ExpandoMetaClass-Mechanismus verwendet. Damit besteht z.B. die Möglichkeit Einheiten an Zahlen anhängen zu können (z.B. "1.km" oder "1.s") und entsprechende Umwandlungen durchzuführen, wie im folgenden Beispiel zu sehen ist.

//Vererbung von neuen Methoden und Eigenschaften welche über
//den ExpandoMetaClass-Mechanismus hinzugefügt werden ist
//standardmäßig deaktiviert. Mit der folgenden Zeile wird
//Vererbung aktiviert.
ExpandoMetaClass.enableGlobally()

Number.metaClass.getProperty = {String einheit ->
    switch(einheit) {
        //delegate ist die Instanz für welche die Eigenschaft
        //abgerufen wird.
        case 'km': return delegate * 1000
        case 'm': return delegate
        case 'cm': return delegate * 0.01
        case 'mm': return delegate * 0.001
        default: 'Die Einheit ' + einheit + ' ist unbekannt.'
    }
}

println(1.km)
println(10.cm)
println(20.ha)

Bei Ausführung des Skriptes entsteht folgende Ausgabe:

1000
0.10
Die Einheit ha ist unbekannt.

Mehr zu dem Thema kann man hier nachlesen.

Abfangen nicht definierter Variablen

In jedem Groovy-Skript existiert eine Instanz der Klasse "groovy.lang.Binding". Diese Instanz löst alle nicht im Skript definierten Variablen auf. Dieser Mechanismus wird u.a. genutzt um Informationen zwischen dem Groovy Skript und der aufrufenden Sprache auszutauschen. Die aktuelle Instanz ist über die Variable "binding" erreichbar. Ersetzt man diese aktuelle Instanz mit einer Instanz einer eigenen Klasse welche von "groovy.lang.Binding" erbt, kann man das Verhalten des Skriptes bei nicht definierten Variablen selber festlegen. Das folgende Beispiel demonstriert diese Möglichkeit.

class TestBinding extends Binding {
    def getVariable(String name) {
      println("Variable nicht gefunden")
      return null
    }
}

binding = new TestBinding()

nichtDefinierteVariable

Die Ausgabe bei Ausführung des Skriptes ist "Variable nicht gefunden", der Rückgabewert ist null.

Überladen von Operatoren

Ähnlich wie in C++ können Operatoren in Groovy überladen werden. Jeder Operator wird durch eine Methode repräsentiert, welche man in der eigenen Klasse einfach implementieren muss, um den entsprechenden Operator zu überschreiben. Eine Liste mit den Methodennamen findet man hier.

class Kaffe {
    List zutaten = []
    
    def leftShift(String zutat) {
        this.zutaten << zutat
    }
    
    def inhalt() {
        println("Im Kaffe ist: ")
        zutaten.each {it ->  print("* "); println(it)}
    }
}

kaffe = new Kaffe()
kaffe << "Wasser" 
kaffe << "gemahlene Bohnen" 
kaffe << "Milch"
kaffe.inhalt();

Ergibt die Ausgabe

Im Kaffe ist: 
* Wasser
* gemahlene Bohnen
* Milch

Methodenzeiger

Es ist möglich Zeiger auf Methoden einer bestimmten Instanz einer Klasse zu erstellen.

class Pflanze {
    def int wassermenge
    def gießen(int menge) {
        wassermenge += menge
    }
}

kaktus = new Pflanze();
def gieße = kaktus.&gießen

gieße 300
gieße 100

Links

Freitag, 4. März 2011

Interne DSL mit Clojure

Clojure ist ein Lisp Dialekt. Es läuft in der Java VM. Ein ausführliches Tutorial zu der Sprache findet man hier. Fast alles in Clojure lässt sich in der folgenden Form darstellen:

([Operation/Funktion] <Parameter 1> ... <Parameter n>)

Es gibt weitere sprachliche Konstruktionen. Die meisten sind allerdings "Syntaktischer Zucker" um den Code lesbarer zu machen. Durch die einfache Syntax scheint Clojure sehr geeignet für interne DSLs zu sein. Der Unterschied zwischen API und DSL ist in Clojure nicht sehr groß.

Installation


Clojure besteht im Wesentlichen aus einer JAR-Datei. Diese kann man sich auf der Projektseite herunterladen. Starten lässt sich der Interpreter dann wie folgt:

java -cp C:\Pfad\zu\clojure.jar clojure.main

Um nicht jedes Mal die ganze Zeile wie oben eingeben zu müssen, lohnt es sich ein Startskript zu erstellen. Unter Windows kann das z.B. ein Batch-Skript mit folgendem Inhalt sein.

@ECHO OFF
java -cp C:\Pfad\zu\clojure.jar clojure.main  %*

@ECHO OFF sorgt dafür, dass bei der Ausführung der folgenden Befehle die Kommandozeile nicht mit ausgegeben wird. Das ist wichtig wenn die Ausgabe eines Clojure Skriptes beispielsweise HTML ergibt und in eine Datei umgeleitet werden soll. %* steht für alle Argumente die dem Batch-Skript übergeben wurden. In diesem Beispiel werden also alle Argumente an den Clojure Interpreter übergeben.

Die Fragebogen-DSL


Einen Fragebogen direkt in HTML-Code zu erzeugen bedeutet viel Schreibarbeit, da viel Overhead erstellt werden muss damit gültiges HTML entsteht. Eine Kfz-Umfrage könnte folgendermaßen aussehen:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>Kfz-Umfrage</title>
  </head>
  <body>
    <h1>Kfz-Umfrage</h1>
    <form action="">
      <h2>Welche Marke hat ihr Fahrzeug?</h2>
      <input type="radio" value="Opel" name="Welche Marke hat ihr Fahrzeug?">Opel<br /‌>
      <input type="radio" value="Renault" name="Welche Marke hat ihr Fahrzeug?">Renault<br /‌>
      <input type="radio" value="BMW" name="Welche Marke hat ihr Fahrzeug?">BMW<br /‌>

      <h2>Wie lautet die Modelbezeichnung?</h2>
      <input type="text" name="Wie lautet die Modelbezeichnung?"<br /‌>

      <h2>Welche Ausstattungsmerkmale hat ihr Fahrzeug?</h2>
      <input type="checkbox" value="Klimaanlage" name="Welche Ausstattungsmerkmale hat ihr Fahrzeug?">Klimaanlage<br /‌>
      <input type="checkbox" value="Navigationssystem" name="Welche Ausstattungsmerkmale hat ihr Fahrzeug?">Navigationssystem<br /‌>
      <input type="checkbox" value="ESP" name="Welche Ausstattungsmerkmale hat ihr Fahrzeug?">ESP<br /‌>

      <br /‌>
      <input type="submit" value="Senden">
    </form>
  </body>
</html>

Der größte Teil des Codes kann in jedem Fragebogen verwendet werden und so liegt es nahe diesen zu generieren. Unterscheiden lassen sich solche Fragebögen an den folgenden Dingen:

  • Titel des Fragebogens
  • Fragen des Fragebogens mit ihrem Typ (Textfrage, Einfache Auswahl, Mehrfache Auswahl)
  • Antwortmöglichkeiten zu den einzelnen Fragen

Mit einer entsprechenden internen DSL in Clojure kann man den Fragebogen von oben in der folgenden Form beschreiben.

(Fragebogen "Kfz-Umfrage"
  (Frage Einfachauswahl "Welche Marke hat ihr Fahrzeug?"
    (Antwort "Opel")
    (Antwort "Renault")
    (Antwort "BMW"))
  (Frage Texteingabe "Wie lautet die Modelbezeichnung?")
  (Frage Mehrfachauswahl "Welche Ausstattungsmerkmale hat ihr Fahrzeug?"
    (Antwort "Klimaanlage")
    (Antwort "Navigationssystem")
    (Antwort "ESP")))

Die verwendeten Sprachkonstrukte wie "Fragebogen" oder "Frage" müssen jetzt nur noch definiert werden.

(def doctype "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
")

(defn Antwort [text]
  text)

(def Einfachauswahl {:Typ :Auswahl, :html "radio"})
(def Mehrfachauswahl {:Typ :Auswahl, :html "checkbox"})
(def Texteingabe {:Typ :Text})

(defmulti Frage (fn [typ frage & antworten] (:Typ typ)))

(defmethod Frage :Auswahl [typ frage & antworten]
  (loop [text (str "<h2>" frage "</h2>"), antworten antworten]
    (if (empty? antworten)
      text
      (recur
        (str
          text
          "<input type=\"" (:html typ) "\""
          " value=\"" (first antworten) "\""
          " name=\"" frage "\" />"
          (first antworten) "<br /‌>")
        (rest antworten)))))

(defmethod Frage :Text [typ, text]
  (str "<h2>" text "</h2>"
    "<input type=\"text\" name=\"" text "\" /><br /‌>"))

(defn Fragebogen [text & fragen]
  (println (str
    (reduce
      str (str
            doctype
            "<html><head><title>"
            text
            "</title></head><body><h1>"
            text
            "</h1><form action=\"\">")
      fragen)
    "<br /‌><input type=\"submit\" value=\"Senden\" /></form></body></html>")))

Speichert man die Definition der DSL in die Datei "Fragebogen.clj" und den Kfz-Fragebogen in die Datei "Kfz-Fragebogen.clj", kann man mit Hilfe der weiter oben angegebenen Batchdatei die HTML-Version des Fragebogens auf die folgende Weiße erzeugen.

clj.bat -i Fragebogen.clj Kfz-Fragebogen.clj > Kfz-Fragebogen.html

Das Symbol ">" sorgt dafür das die Ausgabe des Befehls in die Datei Kfz-Fragebogen.html umgeleitet wird. Nach der Ausführung erhält man den folgenden Fragebogen:

Donnerstag, 3. März 2011

Ziel des Blogs

Ein großes Problem in der kommerziellen Softwareentwicklung ist es, genau das zu entwickeln, was der Auftraggeber verlangt. Dieses Problem besteht, da der Auftraggeber und der Entwickler unterschiedliche Sprachen sprechen. Der Auftraggeber beschreibt das Problem, welches von der Software gelöst werden soll, in seiner Fachsprache. Der Entwickler entwickelt die Software anschließend in einer technischen Sprache wie z.B. Java. Dadurch entsteht Interpretationsspielraum was dazu führen kann, dass der Entwickler an den Anforderungen des Auftraggebers vorbei entwickelt.

Um dieses Problem zu lösen soll ein System entwickelt werden, welches es ermöglicht Akzeptanztests für die Software in der Fachsprache der Domain zu entwickeln. Als Grundlage dafür soll eine Domain-specific language (DSL) dienen. Mithilfe von sprachlichen Mitteln dieser DSL soll es möglich sein, Fachbegriffe der Domäne zu definieren. Diese Definitionen sollen die Brücke zwischen der technischen und fachlichen Welt schlagen. Dadurch soll die Definition der eigentlichen Akzeptanztests mit möglichst wenig technischen Boilerpalte Code auskommen und so möglichst verständlich sein. Der Auftraggeber soll in die Lage versetzt werden, anhand der Akzeptanztests zu erkennen, ob die Software das macht was sie soll. Es soll möglich sein, dass der Auftraggeber sogar selbst Akzeptanztests schreiben kann. Der Entwickler bekommt eine detaillierte Definition der Fachbegriffe. Somit schrumpft der Interpretationsspielraum.
Um das Problemgebiet einzugrenzen, soll dieses System zunächst zur Erstellung von Akzeptanztests für datenbankbasierte Anwendungen entwickelt werden.

Einen ähnlichen Ansatz hat bereits Sebastian Teichgräber gewählt. Seine Arbeit ist ebenfalls in einem Blog dokumentiert. Er verwendete dazu eine externe DSL auf Basis der Eclipse Modeling Foundation. Die Anpassung der DSL an eine spezielle Domain erwies sich bei diesem Ansatz als relativ komplex, da dies zur Compilezeit erfolgen muss und nicht zur Laufzeit erfolgen kann. Um dieses Problem zu umgehen werde ich eine interne DSL auf Basis von Groovy oder Clojure verwendet.
Dieser Blog soll den Verlauf meiner Arbeiten an diesem System dokumentieren.