Testabdeckung für Prozesse messen

Es geht weiter mit unserer Blogreihe "Treat your processes like code – test them!". Heute wollen wir uns ein häufig diskutiertes Thema genauer anschauen - Testabdeckung. Doch welche Rolle spielt die Coverage beim Testen von BPMN-Modellen und wie kann die Abdeckung gemessen werden?

In diesen Post werden wir dazu die folgenden Fragen behandeln:

  • Warum brauchen wir Testabdeckung?
  • Wie können wir die Testabdeckung bei BPMN-Prozessen messen?
  • Wie können wir die Testabdeckung nachvollziehbar verfolgen?

Dazu werden wir das Beispiel aus den vorherigen Posts erweitern. Dieses ist in GitHub verfügbar. Außerdem gibt es einen kleinen Einblick in die Weiterentwicklung der Camunda BPM Process Test Coverage.

Doch warum ist die Testabdeckung eine wichtige Kennzahl für die Entwicklung automatisierter Prozesse? Die kurze Antwort ist: Aus technischer Sicht ist die BPMN eine Programmiersprache, deshalb sollte sie wie Code behandelt werden. Eine hohe Testabdeckung bringt viele Vorteile mit sich, zum Beispiel:

  • Größeres Vertrauen in das Modell: Automatisierte Prozesse beinhalten Ablauflogik, Abhängigkeiten zu Daten und können mit der Zeit wachsen. Ist nicht sichergestellt, dass alle Pfade bei einer Änderungen noch funktionieren, wirkt sich dies negativ auf die Agilität aus. Denn fehlendes Vertrauen bei einer Änderung des Modells führt häufig dazu, dass seltener neue Versionen ausgebracht werden und Abnahmetests aufwändiger werden.
  • Fehler werden frühzeitig erkannt: Beim detaillierten Testen von Modellen können technische und fachliche Fehler im Modell frühzeitig erkannt werden. Denn nicht nur Fehler in der Ablauflogik sind relevant. Beim Testen auf technischer Ebene fallen häufig Zustände im Modell auf, die fachlich überhaupt nicht gewünscht sind.
  • Tests werden definitiv ausgeführt: Wird die Testabdeckung als Kennzahl in die Entwicklung mitaufgenommen und in die Pipeline integriert, dann führt daran kein Weg mehr vorbei. Dadurch entsteht ein größeres Vertrauen in das Deployment. Außerdem führt eine nahtlose und automatisierte Integration dazu, dass das Messen dieser Kennzahl keinen zusätzlichen Mehraufwand darstellt.

Wie kann Testabdeckung bei der Entwicklung automatisierter Prozesse überwacht werden?

Dafür gibt es eine Community Erweiterung: die Camunda BPM Process Test Coverage. Diese visualisiert und überprüft den Abdeckungsgrad des Prozessmodells. Das Ausführen von JUnit-Tests erzeugt html-Dateien im Build-Ordner. Diese sehen wie folgt aus:

Die Erweiterung bietet dabei folgende Features:

  • Visualisierung der Ergebnisse von Testmethoden und Testklassen im BPMN-Modell
  • Visualisierung von Expressions an Sequenzflüssen und Service-Tasks
  • Visualisierung von Transaktionsgrenzen
  • Berechnung und Visualisierung des Abdeckungsgrads

Erweitern wir dazu unser Beispiel. Hierfür brauchen wir zunächst die Branch 02_process_dependencies aus unserem GitHub-Repository. Für den ersten Setup sind lediglich 3 Schritte notwendig:

  1. Maven-Dependencies hinzufügen
    Dafür fügen wir der pom.xml die folgende Dependency hinzu:
<dependency>
   <groupId>org.camunda.bpm.extension</groupId>
   <artifactId>camunda-bpm-process-test-coverage</artifactId>
   <version>0.3.2</version>
   <scope>test</scope>
</dependency>
  1. camunda-cfg.xml anpassen
<bean id="processEngineConfiguration"
   class="org.camunda.bpm.extension.process_test_coverage.junit.rules.ProcessCoverageInMemProcessEngineConfiguration">
   ...
</bean>
  1. TestCoverageProcessEngineRule im Test verwenden
    Passen wir dafür unseren WorkflowTest an und ändern unsere @Rule wie folgt:
@Rule
@ClassRule
public static TestCoverageProcessEngineRule rule = TestCoverageProcessEngineRuleBuilder.create()
        .excludeProcessDefinitionKeys(DELIVERY_PROCESS_KEY)
        .build();

Wichtig ist, dass wir den Delivery Process von der Coverage ausschließen. Denn dieser wird im Test lediglich als Mock bereitgestellt und steht somit nicht zur Verfügung.

Nun können wir den Test ausführen. Die Testergebnisse landen unter ./target/process-test-coverage.

Wir sehen, dass 95% Coverage erreicht wurden, obwohl alles erfolgreich getestet wurde. Dies liegt jedoch an einem Bug in der Coverage-Library, die Compensation-Events nicht richtig nachverfolgt.

Coverage-Minimum festlegen

Schauen wir uns nun an, wie wir ein Coverage-Minimum definieren können, das automatisch geprüft wird. Hierfür haben wir zwei Möglichkeiten:

  • Coverage auf Klassenebene
  • Coverage auf Methodenebene

Diese sind ebenfalls miteinander kombinierbar. Auf Klassenebene können wir die Coverage wie folgt definieren:

@Rule
@ClassRule
public static TestCoverageProcessEngineRule rule = TestCoverageProcessEngineRuleBuilder.create()
        .excludeProcessDefinitionKeys(DELIVERY_PROCESS_KEY)
        .assertClassCoverageAtLeast(0.9)
        .build();

Eine Coverage von 1.0 ist auch aufgrund der vorhandenen Bugs nicht zu empfehlen. Auf Methodenebene können wir eine Coverage-Assertion wie folgt hinzufügen:

@Test
public void shouldExecuteHappyPath() {
    Scenario.run(testOrderProcess)
            .startByKey(PROCESS_KEY, withVariables(VAR_CUSTOMER, "john"))
            .execute();

    verify(testOrderProcess)
            .hasFinished(END_EVENT_ORDER_FULLFILLED);

    rule.addTestMethodCoverageAssertionMatcher("shouldExecuteHappyPath", greaterThanOrEqualTo(0.5));
}

Dabei stehen uns verschiedene org.hamcrest.Matchers zur Verfügung, für diesen Anwendungsfall sind folgende sinnvoll:

  • greaterThan
  • greaterThanOrEqualTo
  • lessThan
  • lessThanOrEqualTo

Die Camunda BPM Process Test Coverage bietet noch ein paar weitere Features, wie das Ausschalten der Coverage auf Klassen- oder Methodenebene. Es lohnt sich somit einen Blick in das GitHub-Repository zu werfen.

Nachvollziehbare Testabdeckung

Die grafische Darstellung der Testabdeckung in Form von HTML-Dateien ist für die lokale Entwicklung ausreichend. Es gibt jedoch speziell zwei Bereiche in denen die Library an ihre Grenzen stößt:

Nachvollziehbarkeit

Die Testabdeckung ist eine großartige Kennzahl, wenn wir sie messen. Aber messen bedeutet auch, dass wir die historischen Daten im Auge behalten. Es geht darum, Verbesserungen oder Verschlechterungen transparent zu machen und damit für Motivation und Nachvollziehbarkeit zu sorgen. Dies bedeutet, dass wir eine Lösung brauchen, um die erstellten Reports zu sammeln und grafisch aufzubereiten.

CI/CD Integration

Auf der einen Seite haben wir die lokale Entwicklung. Dort wird zunächst der Prozess angepasst, dann der dazugehörige Test implementiert und ausgeführt, das Testergebnis geprüft und anschließend beginnt alles von vorn. Ist das Modell und der Test fertiggestellt, wird der entsprechende Code ins Git-Repository gepusht.

In der Build-Pipeline wird dann der Test erneut durchgeführt. Doch wie werden bspw. bei einer Pull-Request die Ergebnisse visualisiert? Wie kann eine Veränderung der Coverage zum vorherigen Stand nachvollzogen werden? Mit der Camunda BPM Process Test Coverage ist das nicht out-of-the-box möglich.

Unter anderem aus diesen Gründen haben wir FlowCov entwickelt – eine Plattform, die genau diese Anforderungen abdeckt. Sie ermöglicht es, im Rahmen der eigenen Build-Pipeline die generierten Reports hochzuladen, um sie grafisch aufzubereiten und allen Stakeholdern zugänglich zu machen. Wer mehr darüber erfahren möchte, dem empfehlen wir unsere Docs.

Ausblick und Entwicklung

Momentan wird die Camunda BPM Process Test Coverage neu entwickelt. Dabei werden verschieden Anpassungen und Features umgesetzt:

  • Möglichkeit zur Implementierung in Junit5
  • Trennung von Coverage-Messung und Report-Erzeugung
  • Neuentwicklung des lokalen HTML-Reports
  • Nahtlose Integration in FlowCov (ohne eigene Rule möglich)
  • Möglichkeit zur Verwendung für Integrationstests

Falls du Lust hast mitzuwirken und eigene Ideen einbringen möchtest, würden wir uns über Contributions und Diskussionen freuen! Die Neuentwicklung findet in diesem Fork statt: https://github.com/holunda-io/camunda-bpm-process-test-coverage/tree/develop/extension