Blog

DMN mit FitNesse in IntelliJ testen

Mit der Decision Model and Notation — kurz DMN — gibt es einen hervorragenden Standard, Geschäftsregeln in Form von ausführbaren Entscheidungstabellen oder Entscheidungsbäumen zu modellieren. Camunda beispielsweise bietet dazu einen kostenlosen und intuitiv bedienbaren Modeler und kann DMN-Tabellen in seiner Engine ausführen. Für UnitTests kann mit einer DmnEngineRule eine schlanke DmnEngine-In-Memory hochgefahren werden, so dass auf der Ebene ohne viel Aufwand getestet werden kann. Gängige Assertions-Frameworks wie AssertJ können für die Überprüfung der Ergebnisse genutzt werden.

Aber das reicht nicht aus. Niemand möchte ernsthaft alle Möglichkeiten, die bei einem Test einer komplexen Entscheidungstabelle getestet werden müssen, in einem JUnit-Test erfassen. Ferner besteht die häufige Anforderung, dass Regeln (auch und besonders) durch den Fachbereich geändert werden können, wobei Tests der Regelwerke erforderlich sind. JUnit ist toll für Entwickler, aber der Fachbereich kann damit nichts anfangen. Und da kommt eine alte Perle ins Spiel: FitNesse.

In FitNesse werden die Testszenarien in einem Wiki erfasst, in Tabellen, fast so wie in Excel. Und das ist genau auf den Business-User zugeschnitten. Tests in FitNesse — sind sie erstmal vom Fachbereich erfasst — können automatisiert von einem Buildserver wie Jenkins aufgerufen werden und somit in den Buildprozess integriert werden.

Und was ist mit dem Entwickler? Was hilft ihm das?

Der Entwickler ist in diesem Prozess dafür zuständig, die Testtreiberklassen zu schreiben. In diesem Tutorial wird gezeigt, wie eine Integration von FitNesse in die Entwicklungsumgebung funktioniert, um das Schreiben der Testtreiberklassen zu erleichtern.

Beispielanwendung

Als Beispiel dient die Ermittlung eines Versicherungsschutzes. Konkret wird ermittelt, ob für einen Kunden ein Schaden in bestimmter Höhe durch den Versicherungsschutz gedeckt ist. Zuerst wird der Kundenstatus anhand der Versicherungsdauer des Kunden und der Angabe, ober er ein VIP ist, ermittelt. Mit dem ermittelten Kundenstatus, dem der Schadenshöhe und der Schadensart kann die zweite Entscheidungstabelle den Versicherungsschutz ermitteln:

Beispielanwendung

Beispielanwendung

Das Modell liegt in src/main/resources/insurance_protection.dmn.

FitNesse-Integration

Jetzt geht es daran FitNesse in die Entwicklungsumgebung zu integrieren. Die neuste Version von fitnesse-standalone.jar von http://fitnesse.org/FitNesseDownload wird nach src/test/resources/ kopiert. FitNesse wird installiert, indem in einer Konsole das Kommando

java -jar fitnesse-standalone.jar -i

ausgeführt wird. Damit wird der FitNesse-Server in dieses Verzeichnis installiert und könnte auch von hier gestartet werden (-i weglassen).

Jetzt fehlt noch das IntelliJ-Plugin für FitNesse. Das Plugin heißt FitNesse und kann in den Plugin-Repositories von IntelliJ oder hier gefunden werden.

Dependencies

Für das Tutorial müssen in die pom.xml nur die Dependencies für die Camunda-Engine und FitNesse eingetragen werden.

<dependency>
    <groupId>org.camunda.bpm.dmn</groupId>
    <artifactId>camunda-engine-dmn</artifactId>
    <version>7.8.0</version>
</dependency>
<dependency>
    <groupId>org.fitnesse</groupId>
    <artifactId>fitnesse</artifactId>
    <version>20161106</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.5</version>
</dependency>

Wiki-Testseite und Treiberklasse

Jetzt kann der erste Test als Wiki-Seite angelegt werden. In FitNesse werden Tests unterhalb der FrontPage angelegt: src/test/resources/FitNesseRoot/FrontPage/CustomerStateDecision.wiki:

---
Test
---
!define TEST_SYSTEM {slim}

|import                      |
|de.holisticon.dmn.testdriver|

|customer state decision  |insurance_protection.dmn|customer_state_decision|
|is vip                   |insurance duration      |customer state?        |
|yes                      |2                       |high                   |
|no                       |11                      |high                   |
|no                       |6                       |middle                 |
|no                       |4                       |low                    |

Die ersten drei Zeilen definieren, dass es sich um einen Test handelt (in FitNesse kann man auch Test-Suites definieren).

Mit !define TEST_SYSTEM {slim} wird das System festgelgt, mit dem FitNesse die Tests ausführen soll. Näheres dazu findet ihr in der Online-Dokumentation.

Die eigentliche Testtabelle beginnt mit der Angabe der Treiberklasse customer state decision. Der Name des DMN-Files insurance_protection.dmn und der Decision-Tabelle customer_state_decision wird dem Konstruktor übergeben:

    public CustomerStateDecision(String dmnFile, String decisionName) {
        super(dmnFile, decisionName);
    }

Eingabeparameter

In der zweiten Zeile stehen die Namen der Eingabeparameter. Die Treiberklasse erwartet für die Eingabeparameter entsprechende Setter:

    private boolean isVip;
    private int insuranceDuration;

    public void setInsuranceDuration(int insuranceDuration) {
        this.insuranceDuration = insuranceDuration;
    }
    public void setIsVip(boolean isVip) {
        this.isVip = isVip;
    }

Für Setter erwartet FitNesse immer eine set-Methode, auch bei Booleans, also hier setIsVip(boolean isVip).

Der letzte Eintrag in der zweiten Zeile customer state? gibt an, welche Methode den erwarteten Wert zurückliefert.

In der korrespondierenden Methode ist der Aufruf der zu testenden Komponente implementiert. Dieser Wert wird von FitNesse mit dem erwarteten Wert aus der Wiki-Seite verglichen.

   public String customerState() {
        return evaluateDecisionTable(createVariables(),"customerState");
    }

DMN-Engine und Decision

Das Erzeugen der DMN-Engine und der Decision aus einer Defaultkonfiguration ist schnell gemacht:

	DmnEngineConfiguration configuration = DmnEngineConfiguration.createDefaultDmnEngineConfiguration();
	dmnEngine = configuration.buildEngine();

	InputStream inputStream = getClass().getResourceAsStream("/" + dmnFile);
	decision = dmnEngine.parseDecision(decisionName, inputStream);

Der DMN-Engine werden die Decision und die Variablen zur Evaluierung übergeben. Das Ergebnis kann dann gegen den erwarteten Wert aus dem Wiki verglichen werden.

	public T evaluateDecisionTable(VariableMap variableMap,String param) {
	    DmnDecisionTableResult results = getDmnEngine().evaluateDecisionTable(getDecision(), variableMap);	    
	    return results.getSingleResult().getEntry(param);	
	}

Die Variablen für die Evaluierung werden aus den Instanzvariablen erzeugt, die FitNesse mit Hilfe der Setter gesetzt hat:

    private VariableMap createVariables() {
        return Variables
                .putValue("isVip", isVip)
                .putValue("insuranceDuration", insuranceDuration);
    }

Testausführung

So wie man es gewohnt ist, können die Tests in IntelliJ ausgeführt werden, indem man beispielsweise mit der rechten Maustaste auf die Wikiseite klickt und im Kontextmenü _run_ oder _debug_ auswählt. In der Konfiguration muss das FitNesse Root directory angegeben sein:

Testkonfiguration

Testkonfiguration

In diesem Test war natürlich alles grün. Wenn die erwarteten Werte richtig sind, werden sie grün hervorgehoben, im Fehlerfall rot. Die Ausgabe in der Konsole sollte so aussehen:

Konsolenausgabe

Konsolenausgabe

Und, was hilft uns das jetzt?

IntelliJ und das FitNesse-Plugin helfen dem Entwickler vor allem darin, die Treiberklassen zu implementieren. Es können bekannte Features wie z.B. Code-Completion oder Code-Formatting genutzt werden. IntelliJ weist schon zur Entwicklungszeit auf Fehler hin:

Wiki Page in IntelliJ

Wiki-Page in IntelliJ

Der größte Vorteil liegt aber darin, dass die Tests sehr einfach in der gewohnten Entwicklungsumgebung aufgerufen und debugged werden können.

FitNesse bietet sich besonders zum Testen von DMN-Tabellen an, weil sowohl die DMN-Tabellen als auch die Wiki-Seiten tabellarisch aufgebaut sind. Allerdings sollte nicht vergessen werden, dass es sich um Business-Rules handelt, die auch vom Business getestet werden sollten. Dazu sollte natürlich nicht die Entwicklungsumgebung genutzt werden, sondern der FitNesse-Server.

Den Code zu dem Tutorial findet ihr bei Holunda: https://github.com/holunda-io/dmn-fitnesse

Über den Autor

Geduldiger Querdenker, in selbstorganisierten Teams Software bauen, Ruhe aus dem Bass des Reggae ziehen und sie beim FCSP wieder verlieren.

Antwort hinterlassen