Die dynamische Doku

Wie Sie Dokumentationen in einer Microservices-Landschaft generieren

Simone Görner, Richard Vogel

©Shutterstock / StockVector

Eine gute Dokumentation gilt als Aushängeschild eines Systems. Doch meist wird sie als notwendiges Übel betrachtet und daher viel zu oft vernachlässigt. Dabei ist eine lückenhafte und veraltete Dokumentation ein risikobehafteter Bereich mit viel Konflikt- und Fehlerpotenzial. Um die Risiken zu vermeiden, haben wir uns auf die Suche nach einer dynamischen und flexiblen Lösung gemacht. Die Ergebnisse stellen wir euch hier vor.

Dokumentationen sind wartungsintensiv und stimmen oft nicht mit der Realität – also dem Code – überein. Der aktuelle Trend, im Enterprise-Umfeld auf Microservices zu bauen, vereinfacht das Problem nicht: Der Endanwender will und soll nicht wissen, dass das System in verschiedene Teilsysteme aufgeteilt ist. Das Handbuch jedoch hat deshalb an dieser Stelle weiterhin monolithischen Charakter. Eine einheitliche, zentrale Dokumentation über verschiedene Systeme, Programmiersprachen und Quellen hinweg stellt eine Herausforderung dar.

In unserem Unternehmen sind wir auf genau dieses Problem gestoßen. Eine Softwaresuite, bestehend aus mehreren Subsystemen – teilweise modular und kundenspezifisch erweiterbar –, bedarf eines nach außen einheitlichen Handbuchs. Da direkt miteinander konkurrierende Firmen diese Software verwenden, darf unter keinen Umständen versehentlich Dokumentation von Kunde A zu Kunde B gelangen. Dennoch soll die Doku stets vollständig und aktuell sein. Und natürlich sollte sie keinen Aufwand generieren.

Einheitliche Sprache

Eine Dokumentation entspringt aus unterschiedlichsten Quellsystemen. Diese sind in unterschiedlichen Sprachen geschrieben und liefern Informationen in diversen Strukturen. Um ein zentrales Dokument zu erhalten, müssen wir zunächst eine einheitliche Sprache für die Dokumentation wählen.

Entwickler lieben Markdown (jedenfalls die meisten). Spätestens seit GitHub jedes Repository mit einer readme.md ansprechend formatiert anzeigt, sollte die Auszeichnungssprache zum Standardrepertoire eines jeden Entwicklers gehören. Mit dem Open-Source-Programm Pandoc lässt sich Markdown komfortabel in verschiedene Formate wie HTML oder PDF, aber auch in EPUP, docx oder in einen modernen Reveal.js-Foliensatz konvertieren.

Wir konzentrieren uns im Folgenden auf die Erstellung eines PDFs, da dieses Format häufig für die Dokumentation einer Enterprise-Software verwendet wird. Wird eine Markdown-Datei mit Pandoc in PDF konvertiert, wird als Zwischenformat LaTeX verwendet. Darum verwundert es nicht, dass alle relevanten Features, die man im Rahmen einer guten Dokumentation benötigt, von Pandoc implizit erfüllt werden.

  • Überschriften unterschiedlicher Größe
  • Textformatierungen fett, kursiv, unterstrichen
  • Hyperlinks mit Titel
  • Listen
  • Referenzen/Verlinkungen innerhalb des Texts
  • Fußnoten
  • Tabellen und Tabellenstyles
  • Bilder mit Bildunterschriften
  • Code sowohl inline als auch im Block
  • Verweise auf Literatur, auch mit unterschiedlichen CSL-Formaten
  • Mathematische Formeln, sowohl inline als auch im Block
  • Literatur-, Abbildungs-, Inhaltsverzeichnisse
  • Verweise auf Literatur samt Verzeichnis
  • Styling mit eigenen Templates

Ein Beispiel-Markdown, in dem die meisten der aufgezählten Features enthalten sind, findet sich in Listing 1. Um es in ein PDF-Dokument umzuwandeln, muss Pandoc nicht unbedingt nativ installiert werden, es gibt zahlreiche Docker Images, die dieses Programm samt LaTeX mitbringen. Der Kommandozeilenaufruf in Listing 2 verwendet das offizielle Image pandoc/latex in der Version 2.6. Die referenzierte Markdown-Datei input.md muss hierzu im aktuellen Verzeichnis liegen. Das Output-Format der Datei result.pdf wird implizit über die Dateiendung bestimmt. Das vollständige Codebeispiel findet man als Gist bei GitHub. Dort ist auch der entsprechende Aufruf für die Windows-Kommandozeile zu finden.

---
title: Dynamische Dokumentation 
author: Simone Görner und Richard Vogel
lang: de
lof: yes
---

# Pandoc
![Pandoc Logo](logo.png){width=15%}

## Kurzvorstellung
[Pandoc](https://pandoc.org) bietet **dicke** und *schräge* Features wie Fußnoten[^note], `inline`-Code sowie

[^note]: mehr siehe @book1.

```java
// Codeblöcke
System.out.println("Mit Highlighting")
```

Da Markdown über $LaTeX$ zu PDF transformiert wird, bekommt man Features wie mathematische Formeln 
$$
e^i\pi=-1
$$
geschenkt. Hierzu gehören auch Tabellen und Abbildungen sowie

- Inhaltsverzeichnis
- Abbildungsverzeichnis.

## Literaturverzeichnis
# Erstellung eines PDFs aus einer Markdown-Datei samt Referenzen im Bibtex-Format
docker run -v `pwd`:/data \
  pandoc/latex:2.6 \
  input.md \
  -F pandoc-citeproc \
  --bibliography references.bib \
  --toc \
  -o result.pdf

Pandoc ist erweiterbar. So gibt es bereits einige Filter, die z. B. chemische Formeln formatieren oder UML-Diagramme aus Code-Snippets erzeugen. Für eigene kleine Anforderungen lässt sich mit der Skriptsprache Lua schnell und einfach ein eigener Pandoc-Filter schreiben.

Markdown dynamisieren?

Fast jeder Entwickler kennt das Problem, dass Dokumentationen doppelt gepflegt werden müssen. Einmal direkt im Code, damit auch weitere Entwickler die Aufgabe der Klasse oder Methode kennen, und zusätzlich noch in einem separaten Dokument, das dem Kunden als fachliche Dokumentation zur Verfügung gestellt wird. Hier ist die Gefahr groß, dass bei einer Änderung nur eine der beiden Stellen angepasst wird und es dadurch zu auseinanderlaufenden Dokumentationen kommt. Dieses Problem lässt sich durch einen dynamischen Ansatz lösen. Prinzipiell steigt bei Entwicklern die Motivation, sich der Dokumentation zu widmen, wenn sie direkt am Code erfolgt, da das auch für die eigene Übersicht hilfreich ist.

Als weitere positive Argumente sind die Versionier- und Wartbarkeit von Markdown-Dateien zu nennen. Da in Softwareprojekten immer (!) eine Versionsverwaltung verwendet werden sollte, können User Änderungen an der Dokumentation direkt nachvollziehen, wodurch sie sich zudem sehr wartungsfreundlich gestaltet.

Der dynamische Ansatz schafft generell eine Möglichkeit zur kontinuierlichen Dokumentation. Automatisierbarkeit spielt eine immer zentralere Rolle, sie ist auch bei der Dokumentation wünschenswert. Für Entwickler fühlt es sich ohnehin angenehmer an, Dokumentationen zu „programmieren“, anstatt sie nur zu schreiben.

Ein weiterer Vorteil ist, dass die Dokumentation testbar ist. Möchte man zum Beispiel einen Kommandozeilenaufruf dokumentieren, kann man zuvor noch mithilfe eines Unit-Tests prüfen, ob er auch korrekt ist. Ebenso lassen sich zur Sicherstellung der Korrektheit von generierten Textdateien relativ einfache Smoke-Tests realisieren. So suche man nach dem Stichwort „Kunde B“ im Dokument für Kunde A, um das Worst-Case-Szenario auszuschließen.

Bei allen Vorteilen des dynamischen Ansatzes dürfen wir die Nachteile nicht aus dem Blick verlieren. Durch die kontinuierlichen Anpassungen an verschiedenen Stellen besteht die Gefahr, dass beim Zusammensetzen Manches nicht mehr so funktioniert wie bisher, ohne dass es direkt bemerkt wird. Die automatisch generierte Dokumentation jedes Mal manuell zu überprüfen, ginge am Ziel vorbei. Daher müssen wir bei einem auftretenden Fehler sicherstellen, dass er in Zukunft nicht erneut auftritt. Wieso nicht einfach per automatisiertem Regressionstest?

Ein weiterer Punkt, der zu Problemen führen kann, ist die steigende Komplexität. Mit jeder zusätzlich genutzten Quelle wächst das benötigte Know-how. Damit fällt es beispielsweise einem neuen Kollegen schwerer, das Konstrukt zu verstehen und zu erweitern – und die Dokumentation sollte selbsterklärend sein und nicht selbst noch einmal dokumentiert werden müssen.

Außerdem ist die ganze Idee auf Markdown und Pandoc beschränkt. Stößt man hier an die Grenzen des Funktionsumfangs, erfordern Erweiterungen der Toollandschaft weiteren zeitlichen Aufwand. Ohne näher darauf einzugehen, wollen wir auf RMarkdown verweisen, eine überaus mächtige Möglichkeit, Markdown-Dokumente zu dynamisieren und mit programmatischen, mathematischen und gestalterischen Elementen zu erweitern.

Modularität gefällig?

In einer komplexen Microservices-Architektur wird Modularität großgeschrieben, obwohl ein zentrales Handbuch bzw. Dokument eher monolithischen Charakter hat. Werden verschiedene Systeme individuell erweitert und gewisse Microservices nur für bestimmte Kunden aktiviert, muss die Rolle der dokumentationserstellenden Einheit überdacht werden.

Bietet ein Microservice die Funktion X an, die mit der Datenquelle Y dokumentiert ist, sollte die Dokumentation nur dann erstellt werden, wenn dieser Service verfügbar ist. Wir wenden hier das bekannte „Inversion of Control“-Pattern an, sodass das dokumentationserstellende Modul lediglich die vorhandenen Schnipsel zu einem zentralen Dokument zusammenfasst. Es kümmert sich somit nicht um den konkreten Inhalt, sondern um die Struktur, das äußere Template sowie den Erstellungsprozess. Ausnahmen bestätigen die Regel: Natürlich wird niemand daran gehindert, den einen oder anderen statischen Text in diesem Modul zu positionieren. Abbildung 1 veranschaulicht diesen Prozess.

Abb. 1: Modularisierter Prozess der Dokumentationserstellung

Abb. 1: Modularisierter Prozess der Dokumentationserstellung

Beispiele sagen mehr als 1 000 Worte

Einige dieser Ansätze wirken abstrakt. Um einen Eindruck gewinnen zu können, wie das in der Realität aussieht, haben wir einige Ideen gesammelt:

  • Erstellen von Release Notes aus Issues Trackers wie Jira
  • Dokumentation eines Kommandozeilentools mit Beispielbefehlen
  • Infrastrukturdiagramme aus einer docker-compose.yml-Konfiguration
  • Benutzerhandbuch mit Screenshots und Arbeitsschritten
  • Darstellung der Swagger-Dokumentation eines REST Service
  • Übersicht von Testergebnissen aus dem aktuellen Jenkins-Lauf
  • Darstellung von Performancezahlen in Diagrammen

Die ersten vier dieser Ansätze werden wir im Folgenden ausführen und dazu Codebeispiele zur Verfügung stellen. Diese Beispiele sollen es euch ermöglichen, die Umsetzung besser nachzuvollziehen und für eigene Projekte zu nutzen.

Beispiel 1: Release Notes

Zur Auslieferung eines Softwareprodukts gehören die Release Notes. Sie beinhalten umgesetzte Stories und behobene Bugs samt kurzer Zusammenfassung und verweisen auf weiterführende Informationen. Auf die Idee, die Release Notes manuell zu pflegen, dürften die wenigsten kommen. Jedes aktuelle Issue-Tracking-Tool beinhaltet eine komfortable Exportfunktion aller Issues, die gewissen Versionen zugewiesen sind.

Für unseren Einsatzzweck suchen wir jedoch nach einer vollautomatisierten Lösung. Für die Arbeit mit Maven bietet sich das Maven-Changes-Plug-in an. Dort lässt sich die Verbindung zu einem Issue Tracker wie Jira oder GitHub konfigurieren, weitere Systeme lassen sich über ein Interface selbst anbinden. In Listing 3 wird eine beispielhafte Maven-Konfiguration dieses Plug-ins gezeigt. Jira-abhängige Parameter wie Status-IDs und Zugangsdaten sind natürlich kontextabhängig anzupassen. Die Aktivierung erfolgt entweder explizit durch den Aufruf mvn changes:announcement-generate -DVERSION=1.2.3 oder implizit z. B. innerhalb der Package-Phase. Ein komplettes Beispiel findet man als Gist.

...
<issueManagement>
  <system>Jira</system>
  <url>https://jira.url/browse/MyProject</url>
</issueManagement>

<plugins>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-changes-plugin</artifactId>
    <version>2.12.1</version>
    <configuration>
      <version>${VERSION}</version>
      <issueManagementSystems>
        <issueManagementSystem>JIRA</issueManagementSystem>
      </issueManagementSystems>
      <webUser>${jira.user}</webUser>
      <webPassword>${jira.password}</webPassword>
      <template>template.vm</template>
      <templateEncoding>UTF-8</templateEncoding>
      <announcementFile>ReleaseNotes.md</announcementFile>
      <resolutionIds>1,6</resolutionIds>
      <statusIds>6</statusIds>
      <filter>project = "MyProject" 
        AND fixVersion = ${VERSION}
        AND issuetype in (Story, Bug)
        AND status = 6
        AND resolution in (1, 6)
      </filter>
    </configuration>
  </plugin>
</plugins>
...

Standardmäßig werden die Issues in einem einheitlichen XML-Format extrahiert. Jedoch bietet das Plug-in die Möglichkeit, die Issues mit einem Velocity-Template in eine benutzerdefinierte Form zu bringen. Das Resultat der Generierung ergibt eine lesbare Markdown-Datei, die unserem Dokumentationsmodul beigefügt werden kann.

Die Issues liegen hier als reiner Fließtext vor. In der Realität fordert der Kunde aus Komfortgründen eine Verlinkung der Nummern zum Issue Tracker ein. Hier können wir den einfachen Einsatz eines Pandoc-Filters demonstrieren. Ein solcher Filter durchläuft jede Zeile eines Markdown-Dokuments und entscheidet nach einem regulären Ausdruck, ob ein Wort durch einen Link ersetzt werden sollte. Da der Issue-Tracker fest ist, hartcodieren wir hier das URL-Pattern. Einen Eindruck eines Lua-Filters findet sich in Listing 4, für die Aktivierung hängt man lediglich –lua-filter=<filtername>.lua an den Befehl an.

local url = "https://jira.url/browse/"

function Str(el)
  if (el.text ~= nil) and (string.find(el.text, "MYPROJECT%-%d+")) then
    story = string.match(el.text, "MYPROJECT%-%d+")
    _url = url .. story
    _title = "Link zur Story " .. story
    return pandoc.Link({pandoc.Str(story)}, _url, _title)
  end
end

Beispiel 2: Kommandozeilenclient

Zu unseren Produkten liefern wir meist ein Kommandozeilentool aus, um Verarbeitungsprozesse automatisiert aufrufbar zu machen, z. B. durch externe Cronjobs. Hier ist es ärgerlich, wenn der konkrete Kommandozeilenaufruf nicht zu der spezifizierten Dokumentation passt. Bei manueller Pflege der Dokumentation ist die Korrektheit schwer automatisierbar zu verifizieren – wir sprechen aus Erfahrung.

Befinden wir uns in einem Java-Projekt, können wir mit Pragmatismus herangehen. Die Voraussetzung ist, dass die einzelnen Kommandos einem Interface folgen, das den Namen, die Beschreibung und die Parameter bzw. Optionen liefert. Nun lassen sich alle Instanzen dieses Interface per Reflection durchackern und dabei lässt sich Markdown-Code erzeugen. Kein schöner und wiederverwendbarer Ansatz, aber zielführend.

Die Open-Source-Bibliothek JCommander nimmt uns bei der Erstellung eines Kommandozeilentools einige unliebsame Arbeiten ab. Das Parsen und Validieren von Eingabeparametern, aber auch die Generierung einer Hilfeseite gehören zum Funktionsumfang. Der konkrete Aufbau dieser Hilfeseite ist glücklicherweise austauschbar und wird an Implementierungen des Interface IUsageFormatter delegiert. Nur noch den selbstgeschriebenen MarkdownFormatter der JCommander-Instanz übergeben, und schon erhalten wir anstatt der Hilfeseite eine Markdown-Datei, die wir unserer Dokumentation hinzufügen können. Eine Beispielimplementierung eines MarkdownFormatters ist in Listing 5 zu sehen. Er führt den Kommandozeilenbefehl aus Listing 6 in das einheitliche Format aus Listing 7. Ein komplettes Beispiel erhält man unter Gist.

Wer ein anderes Kommandozeilen-Framework verwendet, hat vielleicht ebenso Glück. Ähnlich wie JCommander bieten auch Apaches Commons CLI oder das aufstrebende Framework picocli die Möglichkeit, das Rendering der Hilfeseite für unsere Zwecke auszutauschen.

public class MarkdownUsageFormatter implements IUsageFormatter {
  ...
  public void usage(StringBuilder out, String indent) {
    commander.getFields().forEach(
      (k, v) -> out.append("- **")
              .append(k.getName())
              .append("**: ")
              .append(v.getDescription())
              .append("\n"));
    out.append("\n\n").append("## Kommandos\n");
    for (String c : commander.getCommands().keySet()) {
      out.append("\n### Kommando: **").append(c)
         .append("**\n").append("#### Beschreibung")
         .append("\n\n");
      ...
      out.append("#### Parameter\n\n");
      ...
      fields.forEach(
        (k, v) -> out.append("- **")
                .append(k.getName())
                .append("**: ")
                .append(v.getDescription())
                .append("\n")
      );
      out.append("\n#### Beispielaufruf\n\n")
            .append("```\n")
            .append("docker exec client run ")
            .append(c);
        ...
    }
  }
}
@Parameters(commandDescription = "Import der Stammdaten (JSON files).", commandNames = {"importStammdaten"})
public class StammdatenImportJob implements Job {

  @Parameter(names = {"-encoding"}, description = "Encoding der Dateien")
  private String encoding;
  
  @Parameter(names = {"-filter"}, description = "Dateinamenfilter")
  private String filter;
  
  @Parameter(description = "Verzeichnis der zu importierenden Dateien", arity = 1, converter = PathConverter.class)
  private Path targetDir;

  @Override
  public void run() throws ClientException {
    // Konkrete Implementierung 
  }
}
## Kommandos
### Kommando: **importStammdaten**
#### Beschreibung

Import der Stammdaten (JSON files).

#### Parameter

**targetDir** (Hauptargument): Verzeichnis der zu importierenden Dateien

- **encoding**: Encoding der Dateien
- **filter**: Dateinamenfilter

#### Beispielaufruf

```
docker exec client run importStammdaten \
  -encoding <value> \
  -filter <value>\
  <targetDir>
```

Beispiel 3: UML-Diagramme

Verteilungsdiagramme gehören zu einer der Pflichtaufgaben einer gut dokumentierten Softwarearchitektur. Getreu dem Motto „Ein Bild sagt mehr als tausend Worte“ steigert ein solches Diagramm die Qualität der Dokumentation ungemein.

Hält ein Entwicklungsteam das Paradigma „Infrastructure as Code“ ein, gibt es oftmals eine Konfigurationsdatei für ein lauffähiges Gesamtsystem. Wird nun aus dieser Konfiguration ein Diagramm generiert, spart sich der Softwarearchitekt kontinuierlich das Abgleichen von Infrastruktur und UML-Diagrammen. Weichen die Infrastrukturen von individuellen Kundeninstallationen voneinander ab, kann das Generierungsskript über mehrere Installationen hinweg wiederverwendet werden.

Diagramme in versionierbarem Textformat lassen sich beispielsweise mit PlantUML erzeugen. Eine eingängliche Syntax erstellt robuste UML-Diagramme, die man zusätzlich mit einem individuellen Styling optisch schön herrichten kann. Und dank der Erweiterung von Pandoc durch Filter lässt sich die Transformation von PlantUML-Code in den Prozess einbinden.

Aber nun ein konkretes Beispiel: Liefert das Team seine Software in Docker-Containern aus, kann die komplette Infrastruktur in einer docker-compose.yml-Datei beschrieben werden. Auf der Continuous-Integration-Umgebung wird automatisiert gegen dieses Setting getestet, sodass eine Vollständigkeit garantiert ist.

In Listing 8 ist der Anfang einer solchen Konfigurationsdatei angedeutet. Ein nginx-Proxy kapselt mehrere Applikationen wie den Container „webapp“, der wiederum den Container „webappdb“ zum Laufen benötigt. Da jeder Container mit einem Namen, einer Beschreibung und zugehörigen Abhängigkeiten beschrieben ist, lässt sich diese yml-Datei mit einer Skriptsprache einlesen und als PlantUML wie in Listing 9 transformieren. So erhält man aus einer Umgebung mit mehreren Containern ein übersichtliches Diagramm, wie in Abbildung 2 dargestellt. Ein Beispielskript ist in Gist aufgeführt.

Auch die Verwendung einer Konfigurationsdatei für Kubernetes ist als Ausgangspunkt möglich. Wichtig an dieser Stelle ist natürlich, dass genau die Infrastruktur dokumentiert wird, auf der auch getestet wird.

version: '3'

services:
  proxy:
    image: nginx:1.15-alpine
    labels:
      description: "nginx SSL Proxy"
    depends_on:
      - webapp
      ...
  webappdb:
    image: postgres:9.6-alpine
    labels:
      description: "Webapp DB"
  webapp:
    image: enowa/webapp:1.0.0
    labels:
      description: "Webapp"
    depends_on:
      - webappdb
      ...
...
## Verteilungssicht

```uml
@startuml
package "Docker Netzwerk" {
  [nginx SSL Proxy] as proxy
  [Webapp DB] as webappdb
  [Webapp] as webapp
  ...
}
Browser --> proxy
proxy -down-> webapp
webapp --> webappdb
...
@enduml
```
Abb. 2: Automatisch generierte Verteilungssicht als UML-Diagramm

Abb. 2: Automatisch generierte Verteilungssicht als UML-Diagramm

Beispiel 4: Benutzerhandbuch

Kommen wir nun zu einem etwas komplexeren Beispiel, der Dokumentation der Benutzeroberfläche. Hier haben wir hauptsächlich zwei Aufwandstreiber: Zum einen automatisierte Tests des GUI, die oftmals durch einen ferngesteuerten Browser realisiert sind, zum anderen eine Benutzerdokumentation, die meist manuell mit Beschreibungen und Screenshots gepflegt wird. Die Dokumentation veraltet nicht nur direkt, wenn sich etwas an den dargestellten Formularfeldern ändert, sondern auch indirekt, sobald etwas am äußeren Layout geändert wird.

Aufgrund der Testpyramide ergibt sich, dass Tests der Benutzeroberfläche als Integrationstests sehr zeitaufwendig und damit sehr teuer sind. Eine Wiederverwendung würde sich direkt auszahlen. Wir wollen ein Konzept darstellen, das automatisierte Tests mit der Erstellung des Benutzerhandbuchs kombiniert. So erhält man nicht nur die Garantie, dass das Benutzerhandbuch mit dem Ist-Zustand übereinstimmt, sondern erhält dies mit der Sicherheit eines automatisierten Tests.

Ein Test der Benutzeroberfläche lässt sich in drei Zuständigkeitsbereiche gliedern:

  • Bereitstellung einer Datengrundlage
  • Abfolge von Benutzereingaben
  • Verifikation

Wir fügen nun einen Zuständigkeitsbereich hinzu, nämlich denjenigen der Dokumentationsschnipsel. In Listing 10 zeigen wir einen beispielhaften Test, der in Java mit Selenium geschrieben ist. Mit der selbstgeschriebenen Annotation @ImportFile schaffen wir die Datengrundlage (das geschieht nebenbei in Form einer mit JexUnit interpretierten Excel-Datei). Anschließend bedienen wir uns des Fluent Page Object Patterns. Dieses Vorgehen hat zwei Vorteile: Das Schreiben eines GUI-Tests geht Dank Autocomplete leicht von der Hand, andererseits ist der Test von jedermann (auch vom Kunden!) lesbar. Die konkrete Implementierung auf Selenium-Basis bleibt demjenigen verborgen, der die Tests fachlich spezifiziert.

public class SampleIT extends SeleniumTestCase {
  @Test
  @Login
  @ImportFile(name = "brief/Datenbasis.xls")
  public void testErfolgreicherVersand() throws Exception {
    new VertragPage()
      .setLogStrategy(new MarkdownStrategy())
      .title("handbuch.briefversand.title")
      .describe("handbuch.briefversand.description")
      .step("handbuch.vertrag.open.existing")
      .open("Test-4711")
      .step("handbuch.vertrag.brief.click.button")
      .clickSendBrief()
      .step("handbuch.briefe.select")
      .selectBrief(4)
      .takeScreenshot("handbuch.briefe.dialog")
      .step("handbuch.briefe.versandoption.choose")
      .assertDialogVisible(true)
      .chooseVersandOption(Versandoption.LOKALER_DRUCK)
      .clickButtonCreate()
      .step("handbuch.briefe.button.confirm")
      .assertPage(VertragPage.class)
      .step("handbuch.briefe.download.confirm")
      .assertFileDownloadExists();
  }
}

Im Beispiel versendet der GUI-Test zu einem Versicherungsvertrag einen Brief. Neben fachlichen Aktionen wie open und Verifikationen wie assertDialogVisible gibt es Methoden zur Dokumentation. In der Methode logTitle oder logDescription werden die Überschrift und die Beschreibung des fachlichen Anwendungsfalls aufgenommen. Es folgen mehrere stichpunktartige Bearbeitungsschritte, die in der Methode step festgehalten werden. Zwischendurch werden beliebig viele Screenshots erstellt, die zum Schluss an die Dokumentation angehängt werden. Unter Abbildung 3 erhält man einen Eindruck eines fertig generierten Dokuments samt Screenshot. Ein vollständiges Gist findet sich auf Github.

Wie nun mit dieser Information umgegangen wird, hängt allein von der konkreten Implementierung der Log Strategy ab. Im Beispiel wird kein Fließtext verwendet, sondern Verweise auf ein Message Bundle, um eine mehrsprachige Dokumentation zu erhalten.

Abb. 3: Automatisch generiertes Benutzerhandbuch

Abb. 3: Automatisch generiertes Benutzerhandbuch

Fazit

Unsere Beispiele veranschaulichen, dass auf Seiten der Dokumentation eine Automatisierung sinnvoll sein kann. Letztendlich müssen wir von Projekt zu Projekt abwägen, inwiefern sich der (zugegebenermaßen nicht geringe) Initialaufwand lohnt. Aber auch vergleichsweise hohe Aufwände amortisieren sich oftmals mit der Zeit.

Geschrieben von
Simone Görner
Simone Görner
Simone Görner arbeitet seit 2016 für die enowa AG und beschäftigt sich dort mit Themen rund um Testautomatisierung und Softwareentwicklung. Ihre Schwerpunkte sind die Weiterentwicklung des internen Test-Frameworks und die Erstellung automatisierter Testfälle.
Richard Vogel
Richard Vogel
Richard Vogel arbeitet seit 2015 für die enowa AG und beschäftigt sich dort mit Themen rund um Softwareentwicklung und -architektur. Im Fokus stehen modulare und kundenspezifisch erweiterbare Software sowie Microservices mit Docker.
Kommentare

1
Hinterlasse einen Kommentar

avatar
4000
1 Kommentar Themen
0 Themen Antworten
0 Follower
 
Kommentar, auf das am meisten reagiert wurde
Beliebtestes Kommentar Thema
1 Kommentatoren
Christian A. Letzte Kommentartoren
  Subscribe  
Benachrichtige mich zu:
Christian A.
Gast
Christian A.

Schade das man nur Java als Beispiel nutzt vor allem da nodejs z. B. In microservices genauso viel Bedeutung hat wie Java. Wo gibt es dort die Generatoren? Wie schaut es bei php aus? Wenn man sich so einem globalen Thema widmet, sollte man auch mehrere Sprachen berücksichtigen und nicht nur die nehmen, wo es am einfachsten ist.