Blog: Java, Software-Entwicklung & mehr

Dokumentation aus JUnit-Tests generieren

02.03.2022 Michael Mosmann

Beispielcode in Dokumentationen sollte aktuell und korrekt sein. Hier stellen wir ein Tool vor, dass schnell und einfach über JUnit-Tests diese Beispiele erstellt.

Immer wenn man Software entwickelt, entsteht automatisch ein Bedarf an unterschiedlichen Dokumentationen. Wer Software entwickelt, die als Framework oder Library von anderen genutzt wird, sieht sich oft damit konfrontiert, das neben der Dokumentation der Klassen, Methoden und Parametern auch Beispielcode für erwartbare Anwendungsfälle vorhanden ist.

Damit der Beispielcode in der Dokumentation nicht veraltet, ist es sinnvoll, wenn der Code immer aktuell ist. Idealerweise ist der Beispielcode nicht nur aktuell, sondern auch korrekt. Wenn der Beispielcode in einem Test laufen würde, hätte man die Gewissheit, dass die Dokumentation aktuell und korrekt ist.

Die Idee ist folgende: eine Testklasse erzeugt durch ihre Ausführung die Dokumentation. Sowohl der Code als auch die Ergebnisse können in der Dokumentation verwendet werde.

Vorbereitung

Ich habe für diesen Anwendungsfall eine Library entwickelt, die man einfach in eigenen Tests benutzen kann. Dazu muss man zuerst die Dependency hinzufügen:


<dependency>
    <groupId>de.flapdoodle.testdoc</groupId>
    <artifactId>de.flapdoodle.testdoc</artifactId>
    <version>1.3.1</version>
    <scope>test</scope>
</dependency>

Außerdem ist es sinnvoll, im Surefire-Plugin eine Property zu setzen, die angibt, wohin die Dokumentation generiert werden soll. Fehlt diese Angabe, wird die Dokumentation nach dem Testlauf in der Console ausgegeben.


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19.1</version>
    <configuration>
        <systemPropertyVariables>
            <de.flapdoodle.testdoc.destination>
                ${project.build.directory}
            </de.flapdoodle.testdoc.destination>
      </systemPropertyVariables>
    </configuration>
</plugin>

Eine erste Dokumentation

Zuerst müssen wir eine JUnit-Extension im Test registrieren. Dabei kann man diverse Anpassungen vornehmen, auf die ich hier nicht näher eingehen werde.


public class Setup01Test {

  @RegisterExtension
  public static Recording recording = 
    Recorder.with("setup01.md", TabSize.spaces(2));

  @Test
  public void firstTest() {
  }
}

Außerdem benötigen wir noch eine Markdown-Datei, die in den test-resources im selben package liegt:


# Test 01

Wenn wir diesen Test ausführen, wird automatisch eine Dokumentation erzeugt und unter dem Pfad, den wir im Testplugin konfiguriert haben, abgelegt.

Beispiel

Im folgenden möchte ich auf die unterschiedlichen Möglichkeiten eingehen. Bevor ich die Details erkläre, erstellen wir erst einen Test, in dem die verschiedenen Optionen benutzt werden:


public class Setup02Test {
  @RegisterExtension
  public static Recording recording = 
    Recorder.with("setup02.md", TabSize.spaces(2))
      .sourceCodeOf("Example", Example.class);

  @Test
  public void includeSourceCode() {
    recording.include(OtherExample.class, 
      Includes.WithoutPackage, 
      Includes.Trim, Includes.WithoutImports);
  }

  @Test
  public void codeFromMethod() {
    // everything after this marker ...
    recording.begin();

    boolean sampleVar = true;
    assertTrue(sampleVar);

    recording.end();
    // nothing after this marker
  }

  @Test
  public void multipleCodeBlocks() {
    recording.begin();
    // first block
    recording.end();
    recording.begin();
    // second block
    recording.end();
  }
}

Auch das Dokumentationstemplate im Markdown-Format müssen wir anlegen und entsprechend erweitern:


# Test 02

Hier kann beliebiger Text stehen.

## Quellcode aus der Testmethode
...
${codeFromMethod}
...

## mehrere Textblöcke

...
${multipleCodeBlocks}
...

## mehrere Textblöcke - nummeriert

...
${multipleCodeBlocks.1}
...

...
${multipleCodeBlocks.2}
...

## Quellcode einbinden:

...
${Example}
...

...
${includeSourceCode.OtherExample}
...

Wenn der Test dann ausgeführt wird, wird folgende Datei erzeugt:


# Test 02

Hier kann beliebiger Text stehen.

## Quellcode aus der Testmethode

...
boolean sampleVar = true;
assertTrue(sampleVar);
...

## mehrere Textblöcke

...
// first block
...

// second block
...

## mehrere Textblöcke - nummeriert

...
// first block
...

...
// second block
...

## Quellcode einbinden:

/**
 * (c) Michael Mosmann - Concept People
 */
package de.conceptpeople;

public class Example {
  // ...
}
...

...
public class OtherExample {
  // ...
}

Wenn das Template fehlt

Für den Fall, dass man vergessen hat ein entsprechendes Template anzulegen, wird ein Dokument generiert, das auf diesen Umstand hinweist:


# document from generated template

As you did not create a matching template with the name

> 'setup03.md'

in the same package as

> 'de.conceptpeople.Setup03Test'

the content of this file is generated from the 
recordings of your test class.

In your test following parts were recorded:

* 'codeFromMethod'
* 'codeFromMethod.1'

To insert the content of a part into the generated
document you must embed a name
from this list between a starting '${' and '}'.

Erläuterung

Das Erzeugen der Dokumentation basiert auf einem einfachen Prinzip. Die JUnit-Extension wird am Anfang des Tests gestartet und wird informiert, wenn der Test abgeschlossen wurde. Durch den Aufruf von recording.begin() und recording.end() wird ermittelt, in welchem Test und in welcher Zeile der Aufruf stattgefunden hat. Das dient später dazu, die entsprechenden Textzeilen aus dem Quelltext zu kopieren. Außerdem ist es möglich, andere Inhalte, wie z.B. Klassen zu laden und im Dokument zu verwenden. Ebenso kann man Ausgaben aus einem Testlauf in die Dokumentation einfließen lassen. Dazu muss man recording.output(label, content) mit einem eindeutigen Label und dem gewünschten Inhalt aufrufen.

Man sollte darauf achten, dass die erzeugten Dokumentationen immer den gleichen Inhalt erzeugen, damit sie, wenn man diese mit versioniert, nicht bei jedem Testlauf Änderungen aufweisen.

Zusammenfassung

Für kleine bis mittelgroße Projekte kann das ein einfacher Weg sein, eine aktuelle Dokumentation mit Beispielcode zu erstellen, die sowohl aktuell als auch korrekt ist. Markdown wurde gewählt, weil die meisten git-basierten Versionsverwaltungungen Markdown als Dokumentationsformat unterstützen und entsprechend anzeigen können.

Das Projekt findet man auf github unter https://github.com/flapdoodle-oss/de.flapdoodle.testdoc.