Ursprünglich am 27.10.2020 auf dem offiziellen Camunda Blog gepostet: https://camunda.com/blog/2020/10/testing-entire-process-paths/
Warum sollte ich meine Modelle testen? Die kurze Antwort ist: Aus technischer Sicht ist BPMN eine Programmiersprache. Deshalb sollten die Diagramme wie Code behandelt werden. Dafür steht inzwischen eine Vielzahl an Bibliotheken bereit, die das Testen vereinfachen.
Viele dieser Bibliotheken werden wir genauer unter die Lupe nehmen, darunter:
Beim Testen von Modellen stellen sich häufig Fragen wie:
Mit diesen Fragen beschäftigt sich unsere neue Blogreihe “Treat your processes like code - test them!” Wir wollen Best Practices und Vorgehensweisen sammeln, um das Testen zu vereinfachen.
An dieser Stelle gibt es eine kleine Leseempfehlung - die Camunda Best Practices zum Thema Testing. Wir werden uns in den ersten Posts überwiegend im ersten Test-Scope bewegen und Unit Tests mit Java schreiben.
Die Implementierung für diesen Post liegt in diesem GitHub-Repository.
Schauen wir uns dazu folgenden Prozess zur Auftragsabwicklung an:
“Vollständige Prozesspfade” bedeutet von Anfang bis Ende zu testen. Bei komplexen Modellen haben wir schon häufig gesehen, dass Testfälle nur Teile abdecken. In unserem Prozess wäre ein Beispiel dafür, wenn der Abbruch der Bestellung und die Stornierung separat getestet werden.
Der Grund für dieses Vorgehen kann sein, dass die Abläufe davor und danach schon getestet sind oder die einzelnen Testfälle dann viel größer und aufwendiger in der Anpassungen werden.
Aus folgenden Gründen sollten jedoch immer vollständige Prozesspfade getestet werden:
Es gibt jedoch auch Fälle, in denen es durchaus sinnvoll ist, einzelne Aktivitäten eines Prozesses zu testen. Ein Beispiel hierfür sind wiederverwendbare Komponenten. Der Task “Send cancellation” könnte bspw. ein wiederverwendbarer Service Tasks zum Senden von E-Mails sein. Dieser sollte jedoch dann nicht isoliert im Prozess zur Auftragsabwicklung getestet werden, sondern in einem eigenen Scope.
Mit der camunda-bpm-assert Bibliothek können diese kleinen, wiederverwendbaren Komponenten sehr einfach getestet werden. Das Testen von komplexeren Abläufe führt jedoch zu redundantem oder unübersichtlichen Code.
Für das Testen vollständiger Prozesspfade steht deshalb die camunda-bpm-assert-scenario bereit. Schauen wir uns nun ein Vorgehen an, wie mit dieser Library ganze Prozesspfade einfach und effizient getestet werden können. Wer diese Projekt noch nicht kennt, kann zunächst einen genauen Blick ins GitHub Repository werfen.
Standardverhalten definieren: Zunächst wird für alle Elemente ein Standardverhalten definiert. Dies gilt auch für Aufgaben, die nicht auf dem “Happy-Path” liegen, wie der “Cancel Order” Task. Dadurch muss in den einzelnen Szenarien nur noch die Abweichung neu definiert werden.
@Before public void defaultScenario() { MockitoAnnotations.initMocks(this); Mocks.register("sendCancellationDelegate", new SendCancellationDelegate()); //Happy-Path when(testOrderProcess.waitsAtUserTask(TASK_CHECK_AVAILABILITY)) .thenReturn(task -> { task.complete(withVariables(VAR_PRODUCTS_AVAILABLE, true)); }); when(testOrderProcess.waitsAtUserTask(TASK_PREPARE_ORDER)) .thenReturn(TaskDelegate::complete); when(testOrderProcess.waitsAtUserTask(TASK_DELIVER_ORDER)) .thenReturn(task -> { task.complete(withVariables(VAR_ORDER_DELIVERED, true)); }); //Further Activities when(testOrderProcess.waitsAtUserTask(TASK_CANCEL_ORDER)) .thenReturn(TaskDelegate::complete); }
Aktivitäten mit weiterem Verhalten erkennen: In diesem Schritt geht es darum, Aktivitäten zu erkennen, die einen alternativen Output liefern, sodass der Prozess einen weiteren Pfad nimmt. Häufig stehen diese Aktivitäten vor Inklusiven oder Exkulsiven Gateways. In unserem Beispiel trifft das auf zwei Tasks zu.
Der Task “Deliver order” hat sogar zwei weitere Szenarien.
Nachdem die unterschiedlichen Szenarien erkannt wurde, können nun die spezifischen Testfälle implementiert werden.
Implementierung der Testfälle: Um die Implementierung so einfach wie möglich zu halten, werden nur Variablen berücksichtigt, die für den Ablauf des Prozesses relevant sind.
Happy Path
Hierfür muss lediglich das Scenario gestartet werden. Danach kann geprüft werden ob bestimmte Elemente oder End Events abgeschlossen wurden.
@Test public void shouldExecuteHappyPath() { Scenario.run(testOrderProcess) .startByKey(PROCESS_KEY) .execute(); verify(testOrderProcess) .hasFinished(END_EVENT_ORDER_FULLFILLED); }
Send Cancellation
Hierfür muss der Task “Check availability” überschrieben werden:
@Test public void shouldExecuteCancellationSent() { when(testOrderProcess.waitsAtUserTask(TASK_CHECK_AVAILABILITY)).thenReturn(task -> { task.complete(withVariables(VAR_PRODUCTS_AVAILABLE, false)); }); Scenario.run(testOrderProcess) .startByKey(PROCESS_KEY) .execute(); verify(testOrderProcess) .hasFinished(END_EVENT_CANCELLATION_SENT); }
Cancel Order
Hierfür ist es notwendig, einen Error im Task “Deliver Order” zu werfen, anstatt diesen abzuschließen.
@Test public void shouldExecuteOrderCancelled() { when(testOrderProcess.waitsAtUserTask(TASK_DELIVER_ORDER)).thenReturn(task -> { taskService().handleBpmnError(task.getId(), "OrderCancelled"); }); Scenario.run(testOrderProcess) .startByKey(PROCESS_KEY) .execute(); verify(testOrderProcess) .hasCompleted(TASK_CANCEL_ORDER); verify(testOrderProcess) .hasFinished(END_EVENT_ORDER_CANCELLED); }
Deliver twice
Um den Loop mit dem Timer Event zu durchlaufen und anschließend den Prozess abzuschließen, müssen für den Task “Deliver Order” zwei verschiedene Szenarien definiert werden.
@Test public void shouldExecuteDeliverTwice() { when(testOrderProcess.waitsAtUserTask(TASK_DELIVER_ORDER)).thenReturn(task -> { task.complete(withVariables(VAR_ORDER_DELIVERED, false)); }, task -> { task.complete(withVariables(VAR_ORDER_DELIVERED, true)); }); Scenario.run(testOrderProcess) .startByKey(PROCESS_KEY) .execute(); verify(testOrderProcess, times(2)) .hasCompleted(TASK_DELIVER_ORDER); verify(testOrderProcess) .hasFinished(END_EVENT_ORDER_FULLFILLED); }
Mit der camunda-bpm-assert-scenario Bibliothek ist es sehr einfach vollständige Prozesspfade zu testen. Mit dem zuvor beschrieben Vorgehen, lassen sich die definierten Tests effizient und übersichtlich umsetzen. Aber was ist mit Code Abhängigkeiten oder eingebunden Call Activities? Sollen diese mitgetestet werden oder mit Mocking Frameworks versteckt werden? Diesem Thema widmen wir uns im nächsten Post. Bleibt dran!
Falls euch weitere Themen im Testing Bereich interessieren oder ihr eigene Erfahrung teilen wollt, schreibt uns!
Rechtliches