Über unsMediaKontaktImpressum
Maik Heene 22. November 2022

Revisionssicher testen mit Cucumber und Testcontainers

Ob Finanzinstitute, Versicherungen oder allgemein Softwarelieferanten: In vielen Branchen ist Revisionssicherheit ein sehr bedeutsames Thema, da diese die rechtlichen Anforderungen zur Aufbewahrung digitaler Daten vorgibt. Eng damit verknüpft ist das Testen von Software zur Qualitätssicherung sowie das Sicherstellen der Aufbewahrung von erstellten Testfällen, Testdaten, der Applikation (incl. Abhängigkeiten) und der Testplattform. Bei all diesen Aufgaben können verschiedene Anwendungen unterstützen, darunter die beiden Tools Cucumber und Testcontainers. Wie dies in einem konkreten Fallbeispiel funktionieren kann, beschreibt dieser Artikel.

Fallbeispiel: Hintergrundinformationen zur Branche

Mein Unternehmen bietet Methodik-Dienstleistungen und die dazugehörige Software in den Bereichen Banksteuerung, Risikomanagement und Ratingverfahren an. Mit mehr als 1.100 Kunden im Bankenbereich muss das Unternehmen die Mindestanforderungen an das Risikomanagement (MaRisk) der Bundesanstalt für Finanzdienstleistungsaufsicht (BaFin) beachten.

Im Zuge der MaRisk formuliert die BaFin diverse Anforderungen an die Revisionssicherheit der Testfälle: Diese müssen lesbar, leicht verständlich und dokumentiert sein. Daraus resultiert die Herausforderung, die komplexen Testfälle für die Prüfer:innen in geeigneter Weise abzubilden und selbsterklärend aufzubereiten.

Hinzu kommt, dass wir den Ansatz des Test-driven Developments weiter fördern und daher verstärkt in das Behavior-driven Development investieren wollen, um die Kommunikation zwischen Fachexpert:innen, Entwickler:innen und Tester:innen zu stärken. Standard-Testverfahren/-Frameworks sollen dabei unterstützen, eine leicht nachvollziehbare und technisch stabile Umgebung zu gestalten.

Die Testfälle

Um die vorgestellte Teststrategie besser nachvollziehen zu können, wird nachfolgend ein Testfall für die Berechnung der "Kreditportfolio-Modelle Eigengeschäfte" (KPM-EG) vorgestellt. Dieser soll exemplarisch die Herausforderungen verdeutlichen, die beim Testen der Berechnungen bestehen.

Um die KPM-EG Berechnung durchzuführen, bedarf es verschiedener Daten. Hierzu zählen beispielsweise historische Kurse, getätigte Geschäfte der Bank, Gattungsdaten der Finanzprodukte sowie Zinskurven. Dabei wird auf einer großen Datenbasis operiert, die nur schwer außerhalb einer Datenbank festgehalten werden kann. Die Komplexität steigert sich weiter durch die Parametrisierung der Berechnungen wie:

  • Risikobetrachtung,
  • Risikolose Zinsstruktur,
  • Zinsszenario,
  • Credit-Spreadszenario,
  • LGD-Quote,
  • Dauerhafte Wertminderung,
  • Ratingstruktur
  • und viele weitere.

Insgesamt können in die Berechnung 11 Parameter einbezogen und variiert werden, um so verschiedene Risikomodelle zu konstruieren. Durch die Vielfalt der Parametrisierung und die verschiedensten Kombinationsmöglichkeiten ergeben sich mehrere Millionen Permutationen, die, je nach Risikoeinschätzung, nachvollziehbar getestet werden müssen.

Die bisherigen Teststrategien

In diesem Testkontext existierten verschiedene Strategien, um den Anforderungen der revisionssicheren Tests gerecht zu werden. Die lange Zeit gängigste und effektivste Teststrategie stellte der Vergleich berechneter Werte in einer Ascii-Datei dar. Dabei wurde eine zunächst mit der Programmiersprache Smalltalk umgesetzte Applikation mit einer zu testenden Parametrisierung gestartet und die berechneten Werte in eine Datei geschrieben.

[Allgemein]
Datenstichtag = 30.09.2015
TestName = TestEinzelsimulation

[Auswertung]
Ausgabedatei = log.txt
Bewertungstag = 31.12.2015
MarktdatenSzenario = Initialtest
Risikobetrachtung = Migrationsrisiko
Risikolose Zinsstruktur = Swap 3 Monats Tenor
Kalkulationseinstellung = Standard
Steuerung = barwertig
Ratingstruktur = Standard 12 Monate
Dispositionshorizont = 1 Jahr
Zinsszenario = Konstant
Credit Spreadszenario = Konstant
Dauerhafte Wertminderung ab = BB
LGD Quote = Skontro vor Ratingstruktur
Kalibrierungsspread = aktiviert

Eine solche Datei beinhaltete, je nach Berechnung, bis zu mehreren 100.000 Zeilen in den verschiedensten Formatierungen. Beim Vergleich der generierten Datei mit einer Referenzdatei wurden dann die erwarteten und unerwarteten Abweichungen festgestellt. Bei dieser Vorgehensweise führten jedoch nicht nur Differenzen bei den zu testenden Werten, sondern ungewollt auch Abweichungen in der Formatierung (Abweichung einzelner Zeichen) zu Testfehlern. Sämtliche Testfehler mussten daher täglich zeitintensiv manuell analysiert und auf Relevanz geprüft werden und die tatsächlich korrekturbedürftigen Findings anschließend den zuständigen Entwickler:innen zugewiesen werden.

Um bei dieser Testweise die Nachvollziehbarkeit gewährleisten zu können, ist eine genaue Dokumentation erforderlich. Ohne Aufzeichnung und Beschreibung der im jeweiligen Testfall gewählten Parametrisierung, des Vorgehens und der Fehler würde sich zum einen ein ausgewählter Testfall nur sehr schwierig aus der Properties-Datei extrahieren lassen, zum anderen wären Testinhalt und -zweck für Dritte, z. B. neue Teammitglieder oder Prüfer:innen, schwer bis gar nicht nachvollziehbar.

Mit der Überführung der Applikation von Smalltalk nach Java wurde dann die oben beschriebene Teststrategie weiterentwickelt. Auslöser war der Wunsch nach einer übersichtlicheren Parametrisierung der Testfälle sowie der Soll-Werte, dem nun die Testautomatisierung in Java Rechnung tragen sollte. Für die Dokumentation wurde das von den Fachexpert:innen favorisierte Microsoft Excel als Tool auserkoren. In einem Excel-Sheet wurden zunächst verschiedene Testfälle und deren Parametrisierung festgehalten, in weiteren Sheets die dazugehörigen Soll-Daten. Dass darüber hinaus teilweise auch noch weitere zur Berechnung verwendete Daten festgehalten wurden, führte schnell zur Unübersichtlichkeit so mancher Excel-Mappe. Dafür bot Excel jedoch den Vorteil, dass Soll-Ergebnisse innerhalb des Programms berechnet und somit die Korrektheit der Berechnungen gewährleistet werden konnte.

Um aus den Excel-Mappen als Datenbasis einen automatisierten Test zu jedem Testfall erstellen zu können, musste Code erzeugt werden, der die Daten aus der jeweiligen Excel-Datei ausliest und mit den berechneten Werten vergleicht. Aufgrund der Vielzahl an Berechnungen und der unterschiedlichen Formate der Excel-Mappen gestaltete sich die Entwicklung des Codes als relativ aufwändig und umfangreich – gleiches gilt auch für die Wartung des Codes. Die Verständlichkeit der Testfälle konnte durch diese Weiterentwicklung jedoch bedeutend verbessert werden – auch wenn dies ohne weitere Erläuterungen zum Testaufbau in den Excel-Dateien immer noch einiges an Aufwand erforderte.

Testfälle mit Cucumber

Da die bis zu diesem Zeitpunkt erfolgten Evolutionsschritte die geforderten Kriterien der Lesbarkeit, Nachvollziehbarkeit und Dokumentation noch nicht gänzlich erfüllen konnten, wurden nun das Framework Cucumber und die dazugehörige Syntax Gherkin als mögliche Lösung in Betracht gezogen.

Die Parameter der Properties-Datei würde in Gherkin-Syntax nun wie folgt aussehen [1]:

Funktionalität: Abnahmetest für KPM-EG: Deterministische Berichtsgrößen

Es werden die Berichtsgrößen "Aktueller Barwert" , "Erwartungswert (Standard)","
Erwartungswert (Stress)","Forward BW", Gew . Creditspread ","Sichere Perf.",
"Netto Perf .","RPBW t=0","RPBW t=1","Risikoquote(%)","Sicherer Barwert",

Globale Voreinstellungen:
1)Überpari Abschreibung:
a)Handelsbestand: keine Überpari Abschreibung
b)Liquiditätsbestand: keine Überpari Abschreibung
...

Grundlage:
    Angenommen die Docker-Datenbank "nexus/db/kpm-eg/basis-db" wird verwendet
    Angenommen dem Datenstichtag am "30.11.2021"
    Und für das Selektions-Portfolio "KPM-EG"

Szenario: Kalkulationseinstellung 1 mit Risikobetrachtung Migrations & Spreadrisiko
    Angenommen die Risikobetrachtung ist "Migrations und Spreadrisiko"
    Und die Risikolose Zinsstruktur ist "Swap 3 Monats Tenor"
    Und es werden in "Standard" die folgenden Parameter verwendet
        | Parameter-Name              | Parameter-Wert                  |
        | Steuerung                   | barwertig                       |
        | Ratingstruktur              | Standard 12 Monate K2           |
        | Dispositionshorizont        | 31.12.2021                      |
        | Credit-Spreadszenario       | <Konstant>                      |
        | Dauerhafte Wertminderung ab | BBB                             |
        | LGD-Quote                   | Skontro vor Ratingstruktur      |
        | Kalibrierungsspread         | aktiviert                       |
    Wenn die KPM-EG Simulation für das Bewertungsdatum "30.11.2021" ausgeführt wird
    Dann erwarte ich für die unten aufgeführten Geschäfte folge Werte in der KPM-EG Geschäftsliste:
        |  Bezeichnung |  Aktueller Barwert |  Erwartungswert (Standard) |  Erw. GuV-Bel. |  Ausfallrate |
        |  GKMA270005  |  5354911,102       |  2944886,213               |  4043641,557   |  1,306       |
        |  SDZER01     |  1200000,00        |  1197840,90                |  2159,1        |  0,012       |
        |  SDFRN01     |  1200000,11        |  1199133,66                |  866,34        |  0,012       |
        |  KUP23       |  85000,01829       |  70101,71956               |  11329,80128   |  0,059       |

Gherkin bietet den Vorteil einer leicht verständlichen und damit einfach zu erlernenden Syntax. Dies ermöglicht es auch nicht technisch versierten Dritten, den Testfall nachzuvollziehen. Mit dem Keyword "Funktionalität" kann dem Testfall eine eindeutige Überschrift gegeben werden. Unter diesem Keyword befindet sich nun genügend Platz, um den Testfall zu erläutern (in dem Beispiel gekürzt).

Mit dem Keyword "Grundlage" werden Testschritte definiert, die vor jedem Testszenario ausgeführt werden sollen (vergleichbar mit der JUnit-5-Funktionalität @BeforeEach). In dem hier skizzierten Testfall sind dies die Standardparameter sowie die Datenbasis.

Das erste Testszenario wird mit dem Keyword "Szenario" definiert und erhält auch hier eine aussagekräftige Überschrift. Darauf folgend wird der Testfall mit den Keywords "Angenommen, Wenn & Dann" definiert. Mit dem Keyword "Angenommen" wird eine Parametrisierung des Testfalls beschrieben. Das Keyword "Und" wird verwendet, um dem Testfall weitere Bedingungen hinzuzufügen, damit dieser einfacher und lesbarer wird. Im Testschritt mit dem Keyword "Wenn" werden nun die Aktionen benannt und ausgeführt. In diesem Fall soll eine Simulation der Kreditportfolio-Modelle der Eigengeschäfte ausgeführt werden. Nachfolgend wird mit dem "Dann"-Keyword geprüft, ob die Applikation das gewünschte Verhalten aufweist. Hierfür wird untersucht, ob die erwarteten Soll-Werte (in dem Beispiel gekürzt) berechnet wurden.

Damit dieser Testfall automatisiert ausgeführt werden kann, wird nun das Framework Cucumber genutzt, um den benötigten sog. Glue-Code zu schreiben [2]. Als Beispiel soll das Einlesen der Parameter aus der Phrase "Und es werden in "Standard" die folgenden Parameter verwendet" beispielhaft erläutert werden.

Und es werden in "Standard" die folgenden Parameter verwendet
| Parameter Name              | Parameter Wert              |
| Steuerung                   | barwertig                   |
| Ratingstruktur              | Standard 12 Monate          |
| Dispositionshorizont        | 1 Jahr                      |
| Zinsszenario                | <Konstant>                  |
| Credit Spreadszenario       | <Konstant>                  |
| Dauerhafte Wertminderung ab | BB                          |
| LGD Quote                   | Skontro vor Ratingstruktur  |
| Kalibrierungsspread         | aktiviert                   |

Das Keyword "Und" bezieht sich hier auf das Keyword "Angenommen", daher wird mit der Annotation @Angenommen diese Phrase auf eine Java-Methode gemappt:

@Angenommen("es werden in {string} die folgenden Parameter verwendet") <1>
public void es_werden_in_die_folgenden_parameter_verwendet(
        String kpmEgView, <2>
        List<Map<String, String>> viewParameter <3>
) {
    configuration.withConfiguration(new KpmEgViewConfiguration(kpmEgView, viewParameter)); <4>
}
  1. Die Phrase wird auf eine Methode gemappt. So findet Cucumber zu der Phrase den richtigen Glue-Code.
  2. Der Parameter in Anführungszeichen wird an die Variable String kpmEgView übergeben.
  3. Die Wertetabelle wird als List<Map<String, String>> übergeben, wobei ein Objekt in der Liste eine Zeile und die Einträge in der Map jeweils eine Spalte (Key = Spaltenüberschrift) repräsentieren. Es existieren jedoch mehrere Varianten, wie solch eine Datentabelle an den Glue-Code übergeben werden kann [3].
  4. Das parametrisierte Konfigurationsobjekt wird nun an die Injecte Configuration-Bean übergeben.

Dieses Beispiel verdeutlicht sehr anschaulich, dass Cucumber kein komplexes Framework ist, sondern dabei hilft, die Parameter aus der Gherkin-Phrase an den richtigen Glue-Code zu übergeben. Mit der Zeit sammelt sich sehr viel Glue-Code an, da viele verschiedene Phrasen zu mappen sind. Daher ist es ratsam, in Glue-Code nur so viel Code wie nötig zu hinterlegen.

Auch die Ausführung des Testfalls erfolgt nach diesem Prinzip. Am Beispiel der Phrase "Wenn die KPM-EG Simulation für das Bewertungsdatum "30.11.2021" ausgeführt wird" kann gezeigt werden, dass auch hier keine Magie im Spiel ist:

@Wenn("die KPM-EG Simulation für das Bewertungsdatum {string} ausgeführt wird") <1>
public void die_kpm_eg_simulation_für_das_bewertungsdatum_ausgeführt_wird(
        String bewertungsdatum
) {
   ConfigurationBuilder configurationBuilder = configurationGlue.getConfiguration(); <2>
   configurationBuilder.withConfiguration(new Bewertungsdatum(bewertungsdatum));

   final Configuration configuration = configurationBuilder.build(); <3>

   infrastructureGlue.startInfrastructure(); <4>

   TestKpmEgSimulation testKpmEgSimulation = new TestKpmEgSimulation(); <5>
   TestRunner testRunner = new TestRunner(modTestInfoGlue.getTestInfo())
        .withConfiguration(configuration)
        .withTest(testKpmEgSimulation)
        .start();
}
  1. Auch hier wird mit der Annotation @Wenn die Phrase auf die Glue-Code-Methode gemappt.
  2. Aus der Spring-Bean ConfigurationGlue werden die Parameter der @Angenommen-Phrasen geladen.
  3. Und daraus resultierend wird ein Configuration-Objekt erzeugt, das die Berechnung parametrisiert.
  4. In der Spring-Bean InfrastructureGlue wird ein "start" getriggert. Dadurch werden die Docker-Datenbank und die Applikation gestartet.
  5. Zu guter Letzt wird das passende Objekt zum Testen der Berechnung erzeugt und durch den TestRunner gestartet.

Bei der zu erwartenden Masse an Glue-Code ist eine klare Trennung der Verantwortlichkeiten ein Muss: Mit der Trennung in Spring-Beans gilt es, dem Entwurfsprinzip des Separation of concerns nachzukommen [4]. Nach der Ausführung der Testfälle werden die Ist-Ergebnisse der Berechnung mit den erwarteten Soll-Ergebnissen aus dem Testfall verglichen, bei Abweichungen wird ein Fehlerreport erstellt.

Da die Testfälle fortwährend angepasst werden und dadurch immer aktuell bleiben, entsteht eine lebende Dokumentation des gewünschten Verhaltens der Applikation. Diese ist einfach nachzuvollziehen und "menschenlesbar", da sie auf einer natürlichen Sprache mit domänenspezifischen Fachausdrücken basiert. Durch den erzeugten Glue-Code entsteht eine einfache technische Dokumentation zum Nachvollziehen von Funktionalitäten. Noch wichtiger ist jedoch, dass dem Kunden durch das Testen des gewünschten Verhaltens eine funktionierende Applikation mit den gewünschten Funktionalitäten zur Verfügung steht.

Datenlieferung per Testcontainers

Wie anfangs angemerkt, benötigen die Berechnungen zahlreiche unterschiedliche Daten. Zur Datenlieferung wird daher eine relationale Datenbank verwendet, die durch die Fachexpert:innen oder Tester:innen bereitgestellt wird.

Zur Steuerung der Datenbankcontainer kommt das Framework Testcontainers zum Einsatz [5]. Das Framework eignet sich für Container jeglicher Art und ermöglicht es, aus dem Java-Code heraus Docker-Container zu steuern. Hierfür übernimmt es die Kommunikation mit Docker über den docker.socket. Zu Testcontainers existiert eine ausgezeichnete Dokumentation sowie zahlreiche Artikel und Vorträge, sodass hier nicht genauer auf die detaillierte Funktionsweise eingegangen wird.

Als relationale Datenbank ist Firebird das Mittel der Wahl [6]. Bereits vor der Containerisierung wurde diese Datenbank im Unternehmen gerne verwendet. Sie bietet den Vorteil, dass sie alle Daten in einer Datei (.fdb) bündelt und diese somit einfach von Entwickler:innen, Fachexpert:innen und Tester:innen über die Netzlaufwerke getauscht werden können. Genau dieser Vorteil begünstigt eine einfache Containerisierung. Zusätzlich ist das Basis-Docker-Image von Firebird lediglich ~80 MB groß – dies ermöglicht auch in Zeiten von Homeoffice und VPN einen schnellen Download [7].

Daher wird nun zu unserem Testfall ein Image aus dem Basis-Docker-Image von Firebird (das Datenbanksystem) und der verwendeten Firebird-Datei erzeugt. Durch dieses Vorgehen lassen sich die Daten ganz simpel in einer Docker-Registry verwalten und versionieren. Insbesondere die Möglichkeit zur Versionierung stellt einen bedeutenden Pluspunkt dar und spricht für die Vorgehensweise der Containerisierung. Wird die Docker-Registry in dem Repository-Manager Sonatype Nexus betrieben, ergeben sich zusätzlich Quick Wins, die zur revisionssicheren Verwaltung benötigt werden [8].

Zu jeder Datenbank-Image-Version werden Meta-Informationen gespeichert wie: Erstellungsdatum, IP und Benutzername des Uploader, Prüfsummen in sha1 und sha256 und Änderungsdatum des Images.

Starten des Datenbank-Containers im Glue-Code

Zum Starten des Datenbank-Containers bedarf es nur weniger Zeilen Code. Durch die Phrase "Angenommen die Docker-Datenbank nexus/db/kpm-eg/basis-db wird verwendet" wird der relevante Parameter der Lokation des Images im Nexus übergeben. Mit diesen Informationen und dem Framework Testcontainers ist es nun problemlos möglich, die Datenbank über Docker zu starten:
 

@Angenommen("die Docker-Datenbank {string} wird verwendet")
public void die_docker_datenbank_wird_verwendet(String dockerImage) {

    assumeThat(dockerImage).isNotEmpty();
    DockerImageName image = DockerImageName <1>
        .parse(dockerImage)
        .asCompatibleSubstituteFor("jacobalberty/firebird");

    databaseContainer = new FirebirdContainer(image) <2>
        .withDatabaseName("databasename")
        .withUsername("username")
        .withPassword("password");

    databaseContainer.withImagePullPolicy(PullPolicy.ageBased(ONE_DAY)); <3>

    databaseContainer.start(); <4>
}
  1. Damit das passende Image heruntergeladen wird, muss das Image definiert werden. Hierzu wird im parse die Lokation des Datenbank-Images in Nexus übergeben und als .asCompatibleSubstituteFor die Lokation des Basis-Images von Firebird.
  2. Mit diesem Image wird ein Testcontainers-Objekt vom Type FirebirdContainer erzeugt, außerdem können zusätzliche Parameter zur Konfiguration übergeben werden.
  3. Als sog. Pull-Policy wird Testcontainers der Wert von einem Tag übergeben [9]. Wird die Pull-Policy nicht verändert, so wird Testcontainers das Image nicht aktualisieren, wenn es bereits heruntergeladen worden ist. Jedoch kommt es innerhalb der Entwicklung dazu, dass bestehende Datenbanken mehrmals angepasst werden müssen und sich somit auch das Image verändert. Mit dieser Option prüft Testcontainers (einmal am Tag) die Prüfsummen des Images im Speicher sowie in der Docker-Registry. Stimmen diese nicht überein, wird das Image erneut heruntergeladen.
  4. Nachdem alles konfiguriert ist, muss der Container noch gestartet werden. Dies geschieht in weniger als einer Sekunde, wenn das Image bereits im Speicher liegt.

Die größten Herausforderungen an diesem Vorgehen bestehen darin, das Datenbank-Schema aktuell zu halten und den Speicherbedarf auf dem Server sicherzustellen. Die Aktualisierung des Datenbank-Images übernimmt hier beispielhaft ein simples Script, das das Datenbank-Image herunterlädt, den Datenbank-Container startet und das Schema der Datenbank z. B. über Flyway aktualisiert [10]. Die Firebird-Datei wird nach Fertigstellung aus dem Datenbank-Container gezogen und aus dieser ein aktualisiertes Datenbank-Image auf Basis des Firebird-Images erzeugt.

Die Verwendung von Datenbank-Containern erlaubt es nun, dass Datenbanken einfach versioniert und durch Zugriffsbeschränkungen im Nexus verwaltet werden können. Des Weiteren wird die Datenbank nur während der Laufzeit eines Testfalls bzw. mehrerer Testfälle beansprucht. Nach Abschluss des Tests wird der Container gestoppt und wichtige Server-Ressourcen somit wieder freigegeben (im Gegensatz zu einer laufenden Datenbank im Netzwerk, die auch dann Ressourcen auf dem Server belegt, wenn sie nicht aktiv genutzt wird). Durch das Stoppen wird die Datenbank wieder in ihren Ursprungszustand zurückgesetzt und kann anschließend für den nächsten Testfall sofort wieder gestartet werden. Das schnelle Starten und Stoppen der Datenbank ermöglicht es, in den Testfällen Daten zu manipulieren. Da jeder Testprozess eine eigene Instanz der Datenbank startet, können konkurrierende Zugriffe durch anderweitig laufende Tests verhindert werden. Leider kommt es bei Netzwerk-Datenbanken oft zu Testfehlschlägen, da von Kolleg:innen lokal parallel gestartete Tests die Datensätze in den Datenbanken blockieren.

Verwaltung und Ausführung der Testfälle

Um die Frage nach der Revisionssicherheit vollständig bejahen zu können, muss nun noch die revisionssichere Verwaltung der Testfälle und deren Testergebnisse gewährleistet werden. Hierfür eignet sich ein Git-Repository oder ein Testmanagement-Tool wie z. B. Xray (ein Plugin in Atlassian Jira) [11]. In Xray können die Cucumber-Testfälle einfach verwaltet werden. Es ermöglicht außerdem, neben automatisierten auch manuell ausgeführte Testfälle zu verwalten und zu dokumentieren. Wird eine Verbindung zwischen Feature und Testfall erstellt, lässt sich so relativ einfach ein Report erzeugen, der Kund:innen und Auditor:innen zur Verfügung gestellt werden kann.

Das Testmanagement-Tool bietet nun die Möglichkeit, die Testfälle über eine API herunterzuladen und die Testergebnisse zurückzuspielen [12]. So werden die Testausführungen und deren Ergebnisse im Tool dokumentiert. Der CI-Server (hier Jenkins) lädt die Testfälle herunter und führt sie auf der Testplattform (auf einem Kubernetes-Cluster) mit JUnit, Cucumber & Testcontainers aus. Als Ausführungsreport liefert Cucumber eine report-data.json.

Maik Heene auf den IT-Tagen 2022

Zum gleichen Thema hält Maik Heene einen Vortrag auf den diesjährigen IT-Tagen – der Jahreskonferenz der Informatik Aktuell.

Revisionssicher testen mit Cucumber & Testcontainers
(14.12.2022, 13:00 Uhr)

Da die Testfälle auf einer voll initialisierten Applikation ausgeführt werden und dies viele Speicher- und CPU-Ressourcen beansprucht, ist es ratsam, die Ausführung in ein Cluster zu verlagern. Allerdings besteht bei Testcontainers die Herausforderung, dass das Framework Docker und somit einen docker.socket benötigt. Da Kubernetes dies nicht unterstützt, wird hier das Projekt "kubedock" verwendet [13], das Befehle über einen docker.socket annimmt und sie kubernetes-konform übersetzt und ausführt. Dabei handelt es sich bei "kubedock" nur um eine minimale Implementierung der Features von Docker. Ein simples Starten und Stoppen der Container ist hierüber einfach möglich, bei komplexeren Befehlen gerät jedoch auch diese Lösung schnell an ihre Grenzen.

Ist dieser Ansatz nun revisionssicher?

Da die Frage der Verwaltung der Testfälle nun geklärt ist, kann nun die Revisionssicherheit der Artefakte Programmcode und Glue-Code, abhängige Bibliotheken, erzeugte Versionen, Testfälle, Testdaten und Ausführungsbelege betrachtet werden. Ob der Prozess revisionssicher ist, lässt sich anhand der Kriterien

  • Vollständigkeit,
  • Wiederauffindbarkeit,
  • Unveränderbarkeit,
  • Nutzung nur durch Berechtigte,
  • Sicherung vor Verlust,
  • Nachvollziehbarkeit,
  • Prüfbarkeit,
  • Ordnungsmäßigkeit,
  • Achtung der Aufbewahrungsfristen und
  • Dokumentation

feststellen.

Dass die o. g. Artefakte vollständig und sicher im Archiv angekommen sind, kann in den meisten Fällen im Nexus durch Prüfsummen sichergestellt werden. Bei den Testfällen wird diese Aufgabe vom Testmanagement-Tool übernommen. Die Unveränderbarkeit und die Nutzung durch Berechtigte kann in den jeweiligen Tools durch ein striktes Rechtekonzept umgesetzt werden. Durch einen Index und Metadaten in Jira, Nexus und Git kann gewährleistet werden, dass alle Artefakte einfach wieder auffindbar sind. Wird ein Testfall geprüft, so ist im Testmanagement-Tool hinterlegt, um welche Version der Software es sich handelt und mit welcher Datenbasis dieser Testfall durchgeführt worden ist. Mit diesen Informationen ist es dann ein Leichtes, den Programmcode aus Git und die Artefakte aus Nexus zu laden. Alle genannten Systeme können durch ein Backup gesichert werden, womit einem eventuellen Verlust vorgebeugt wird.

Es ist darauf zu achten, dass dabei die rechtlichen Aufbewahrungsfristen eingehalten werden. Da die Testfälle nun in menschenlesbarer Sprache verfasst und somit ohne fachspezifische Vorkenntnisse für Kund:innen und Auditor:innen zu verstehen sind, wird das Kriterium der Nachvollziehbarkeit weitgehend erfüllt. Dazu gehört natürlich auch, dass der Testfall in der spezifischen Domäne einen Sinn ergibt. Zu jedem Zeitpunkt muss gewährleistet sein, dass auch mehrere Jahre alte Software prüfbar ist, die noch beim Kunden im Einsatz ist. Dies bedeutet, dass die Applikation auch Jahre später noch kompilierbar ist, Testfälle ausführbar sind und die Applikation zum Prüfen ausgeführt werden kann. Um dies sicherzustellen, ist es zwingend notwendig, applikationsabhängige Bibliotheken (z. B. von Maven-Central) im Nexus zu archivieren. Geschieht dies nicht, kann es sein, dass die Bibliotheken nicht mehr zur Verfügung stehen und die Applikation nicht mehr kompilierbar ist. Zudem muss das Vorgehen gut dokumentiert werden, damit Personen das revisionssichere Verfahren schnell verstehen und in den Entwicklungsteams umsetzen können.

Wann sollte diese Teststrategie verfolgt werden?

Diese Teststrategie bewegt sich auf der Ebene der Abnahme- bzw. End-to-End-Tests. Durch die vollständige Initialisierung der Applikation werden viele Ressourcen benötigt und damit einhergehend ist die Ausführungszeit erheblich höher als bei einem Unit-Test. Des Weiteren wird für die Archivierung der Artefakte (insbesondere der Datenbank-Images) viel Speicherplatz verbraucht. Die Verwaltung der Testfälle im Testmanagement-Tool erzeugt zudem einen erhöhten Verwaltungsaufwand, der jedoch durch ein einfaches Reporting an Kund:innen oder Auditor:innen teilweise kompensiert werden kann.

Aus diesen Gründen sollten daher nur Testfälle mit einer hohen Wertigkeit, prüfungsrelevante oder dokumentationspflichtige Testfälle mit dieser Teststrategie umgesetzt werden. Fachexpert:innen und Tester:innen werden zudem durch die einfache Testfallerstellung und automatisierte Durchführung der Testfälle dazu motiviert, intensiver zu testen.

Autor

Maik Heene

Maik Heene ist seit mehr als 10 Jahren Softwareentwickler und aktuell im Bereich der Testautomatisierung bei der parcIT GmbH tätig.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben