Jeder, der über einen längeren Zeitraum mit Mule gearbeitet hat und (hoffentlich!) MUnit-Tests geschrieben hat, ist vermutlich auch schon einmal auf die so genannten Matcher gestoßen. Falls nicht, ist das auch nicht schlimm.
Matcher sind im Prinzip Helferfunktionen, welche es ermöglichen, sowohl einfache als auch sehr komplexe Assertions durchzuführen.
So kann man zum Beispiel mit der folgenden Assertion prüfen, ob alle Werte in einem Array eine Nummer sind:
%dw 2.0
import dw::tests::Asserts
---
[1,2,3] must eachItem(beNumber())
Oder mit dem Folgenden, ob ein Key in einem Objekt existiert:
%dw 2.0
import dw::tests::Asserts
---
{ name: "Pasquale", lastName: "Brunelli" } must haveKey("name")
Sollte eine Assertion nicht erfüllt sein, schlägt der Testfall entsprechend fehl.
Insgesamt gibt es in Mule einige Build-in-Matcher, welche das Erstellen von Assertions in MUnit-Tests wesentlich vereinfachen können.
In diesem Blogpost soll es aber gar nicht um die Build-in-Matcher gehen, sondern um die Möglichkeit, eigene Matcher zu erstellen. In meinen Projekten fällt mir dabei immer wieder auf, dass von dieser Möglichkeit kaum gebrauch gemacht wird, obwohl es durchaus sinnvolle Szenerien für Matcher gibt. Stattdessen werden regelmäßig mehr oder weniger komplexe Assertions von einem Test in den Nächsten kopiert und dabei vor allem Wart- und Lesbarkeit stark reduziert.
Ein Beispiel
Nehmen wir zum Beispiel einmal an, wir wollen validieren, dass unser Prozess tatsächlich valide (deutsche) KFZ-Kennzeichen erstellt. Dafür wird erwartet, dass wir für eine größere Anzahl von Kennzeichen MUnit-Tests schreiben, um sicherzustellen, dass keine irregulären Kennzeichen erzeugt werden können.
Unter Zuhilfenahme einer Regular Expression prüfen wir also verschiedene Beispiel Kennzeichen, ob diese valide sind. In jedem dieser MUnit-Tests haben wir dann, je nach Anzahl der nötigen Testfälle eine mehr oder weniger große Anzahl an MUnit-Flows, welche alle die Gleiche - aber nicht die selbe Regular Expression für diesen Zweck nutzen.
%dw 2.0
output application/java
---
payload matches /^[A-ZÖÜÄ]{1,3} [A-ZÖÜÄ]{1,2} [1-9]{1}[0-9]{1,3}$/
Wenn sich die Regular Expression nun verändert (was bei Nummernschildern zugegebenermaßen eher selten vorkommt), müsste man diese RegEx an mehreren Stellen anpassen. Auch das Verständnis, was genau der Code abbildet, ist für andere Kollegen, die an dem Code arbeiten, nicht unmittelbar gegeben.
Da wäre es doch schöner, wenn dieses Script stattdessen wie folgt aussehen würde:
%dw 2.0
import dw::tests::Asserts
---
"MAX MU 2000" must beNummernschild()
Da sieht dann wirklich jeder Betrachter direkt, was passiert, oder?
Wie geht das nun?
Ein Custom-Matcher ist ganz einfach erstellt.
Zuerst erstellen wir eine Datei im "src/test/resources/dwl/matcher" Ordner unseres Mule-Projektes. Diese nennen wir NummernschildMatcher.dwl.
In diese können wir folgenden Code kopieren:
%dw 2.0
import * from dw::test::Asserts
fun beNummernschild(): Matcher<String> =
(actual:String) -> do {
var isNummernschild = actual matches (/^[A-ZÖÜÄ]{1,3} [A-ZÖÜÄ]{1,2} [1-9]{1}[0-9]{1,3}$/)
---
{
matches: isNummernschild,
description: {expected: " to be an Nummernschild.", actual: write(actual) as String}
}
}
Wir haben nun eine Funktion beNummernschild()
definiert, welche einen String als Parameter erwartet, und diesen dann gegen unsere Regular Expression validiert und das Ergebnis als Boolean in der Variable isNummernschild
ablegt.
Als Rückgabe wird der Wert matches
erwartet. Dieser erhält das Resultat aus der vorangegangenen Validierung (Natürlich hätte man diese auch ohne die Nutzung einer Variable direkt in das Feld schreiben können!). Das Object description
wird beim Ausführen der Tests genutzt, wenn die Assertion fehlgeschlagen ist.
Jetzt ist es bereits so weit und wir können unseren Nummernschild-Matcher innerhalb unserer MUnit-Testfälle nutzen, um herauszufinden, ob wir im Payload ein reguläres Nummernschild erhalten haben:
import * from dw::test::Asserts
---
payload must dwl::matcher::NummernschildMatcher::beNummernschild()
Das Verhalten können wir natürlich auch invertieren, wenn wir kein Nummernschild erwarten:
import * from dw::test::Asserts
---
payload must notBe(dwl::matcher::NummernschildMatcher::beNummernschild())
Die dazugehörenden Flows sehen dann so aus:
Es ist aber auch möglich, unsere Assertion mit existierenden Funktionalitäten zu mischen, um so zum Beispiel mehrere Werte gleichzeitig zu überprüfen. Sollte hier ein Element des Arrays keinem Nummernschild entsprechen, liefert eachItem
ein Array mit dem Index des bzw. der fehlerhaften Elemente.
import * from dw::test::Asserts
---
payload must eachItem(dwl::matcher::NummernschildMatcher::beNummernschild())
Fazit
MUnit Custom-Matcher sind keine Raketenwissenschaft - und mein Beispiel natürlich auch nicht. Vielmehr finde ich es wichtig, dass man sich der Möglichkeiten, welche Mule bereitstellt, bewusst ist und somit die besten Vorraussetzungen hat, verständlichen, wartbaren und - daraus resultierend - schönen Code zu schreiben.
Ab wann es sich für eure Testfälle lohnt, müsst ihr natürlich selber abwägen. Ich bleibe jedoch dabei, dass ich MUnit-Tests, bei welchen ich Custom Matcher benutze, mit gutem Gewissen an meine Kunden übergeben kann.
Als nächsten Schritt, wenn es zum Beispiel branchenspezifische Muster gibt, welche in vielen verschiedenen Projekten genutzt werden sollen, könnte man seine Matcher in ein DataWeave Library Projekt auslagern und somit dem gesamten (Teil-)Projekt oder gar Unternehmen eine einheitliche Basis an Matchern zur Verfügung stellen. Spätestens dann macht es auch Sinn, eigene Tests für die Matcher zu schreiben, um die Korrektheit sicherzustellen.
Ich bedanke mich für euer Interesse. Bei Fragen oder Kritik dürft ihr mich wie immer gerne kontaktieren!
Weitere Beiträge
von Pasquale Brunelli
Dein Job bei codecentric?
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.