adesso Blog

Menschen, die zusammen an einem Tisch sitzen

Knowledge Sharing ist eines der Kernprobleme unserer Branche und zentraler Bestandteil vieler moderner Methodiken der Softwareentwicklung. Specifications by Example als Vorgehen kann hierbei unterstützen und bietet einen Rahmen, um eine gemeinsame Wissensbasis zu schaffen, Randfälle durch Tests im Vorfeld zu beschreiben und als Ergebnis eine lebende Spezifikation zu erstellen.

Specification by Example

In diesem Artikel beschäftigen wir uns zuerst mit der Grundidee von Akzeptanztests, ihrer unterstützenden Funktion und der Idee der lebenden Spezifikation. Anschließend untersuchen wir drei Tools und sehen anhand von einfachen Beispielen, wie man sie in der Praxis einsetzen kann und sprechen über ihre Vorzüge und Schwächen.

Was genau sind also Akzeptanztests?

Akzeptanztests

Wenn wir Wikipedia dazu befragen bekommen wir eine sehr allgemeine Antwort: Akzeptanztests sind demnach also Abnahmetests, die vom Benutzer oder von einem entsprechenden Fachexperten durchgeführt werden nachdem die Software erstellt wurde und dienen zur Überprüfung, ob eine Software ordnungsgemäß funktioniert und zur Vermeidung von Regression.

Wie das bei allgemeinen Antworten so üblich ist, findet man auch hier interessante Lücken: Wer schreibt diese Tests und vor allem wann?

Wenn man sich Projekte ansieht, ist die Erwartung, sowohl in Teamstrukturen nach Conway als auch bei cross-funktionalen Teams, dass diese Art von Tests vom Testteam geschrieben und durchgeführt werden. Und das erfolgt entweder parallel zur Entwicklung oder in irgendeinem nachgelagerten Prozess.

Findet hierbei jetzt irgendein Austausch statt?

Knowledge Sharing

In der Praxis findet dabei dann allenfalls irgendeine Form der Kommunikation statt, sobald Probleme auftreten und etwas nicht wie erwartet funktioniert, eine Fehlerklasse gänzlich übersehen wurde oder aufgrund der konkreten Umsetzung neue Fehlerfälle entstehen.

Hier wäre es jetzt von Vorteil, wenn man die Sicht beider Gruppen einfängt: Das Entwicklungsteam betrachtet die Software von seiner technischen Seite und versucht, alle ihm bekannten Probleme im Vorfeld zu berücksichtigen und zu verhindern. Das Testteam nutzt seine Kenntnis des Produktes, Erfahrungen allgemein beim Testen von Software und natürlich Testheuristiken, um Fehler aufzuspüren.

Wie so oft in unserer Branche ist Kommunikation der Schlüssel für dieses Problem: Setzen sich beide Teams gemeinsam an einen Tisch, um im Vorfeld über mögliche Probleme zu sprechen, könnten aufwändige Nacharbeiten begrenzt und die Abläufe beschleunigt werden.

Three Amigos

Three Amigos oder Specification Workshop ist ein Format, um genau dieses Vorgehen zu ermöglichen: Wenn ein neues Feature geplant wird, kommen der beauftragende Stakeholder, ein Verantwortlicher aus dem Entwicklungsteam und ein Verantwortlicher aus dem Testteam zusammen und besprechen, was und vor allem wie dies umgesetzt werden soll und wie später überprüft werden kann, dass die Aufgabe abgeschlossen wurde.

Hierbei ist dieses Format vollkommen offen, mögliche Ergebnisse können einfache Notizen (beispielsweise an der Userstory im agilen Feld) oder komplette Tabellen mit konkreten Randbedingungen sein. Wichtigstes Element hier ist die Kommunikation und das im Gespräch entstehende gemeinsame Verständnis der Aufgabe der Beteiligten.

Im nächsten Teil sehen wir uns drei verschiedene Tools an, die hierbei unterstützen können und von denen ein wenig die weiteren Schritte abhängen.

Tools

Die nächsten Beispiele zeigen anhand eines konkreten Testfalls an einer einfachen Todo-App (auf Basis von Quarkus) wie Todo-Einträge erzeugt und die hier vorgestellten Tools verwendet werden können.

Die Todo-App selbst bietet eine einfache REST-Schnittstelle, über die sie u.a. Daten entgegennimmt und in-memory persistiert.

Sämtliche Beispiele können im folgenden Repository eingesehen werden:

https://github.com/unexist/showcase-acceptance-testing-quarkus

Cucumber
Einleitung

Als erstes Tool wollen wir uns Cucumber ansehen, welches vermutlich das bekannteste zum Thema Behavior-Driven Development (kurz BDD) ist. Cucumber verwendet ein leicht verständliches given-when-then-Format (ursprünglich von Dan North entwickelt) namens Gherkin, um strukturierte Testfälle zu schreiben und sukzessive eine Domain-Specific Language für die Fachdomäne zu entwickeln.

Hierbei ist die Ähnlichkeit zwischen given-when-then und dem von Connextra geprägten Format kein Zufall: Die Grundidee hier ist, es soll so einfach wie möglich sein, die eine Form in die andere zu übertragen.

Gherkin selbst bietet zahlreiche Möglichkeiten und ist alleine dadurch schon einen eigenen Blogpost wert, daher beschränken wir uns hier auf die verwendeten Grundbefehle. Im folgenden Beispiel kann man schön sehen wie ein Testfall (oder Feature) aufgebaut ist:

Beispiel
Feature: Create a todo
  Create various todo entries to test the endpoint.
  Scenario Outline: Create a todo with title and description and check the id.
    Given I create a todo with the title "<title>"
    And the description "<description>"
    Then its id should be <id>
    Examples:
      | title  | description  | id |
      | title1 | description1 | 1  |
      | title2 | description2 | 2  |

Im oberen Teil wird zunächst der allgemeine Testfall beschrieben und anschließend in einem konkreten Szenario noch einmal eingegrenzt. Darauf folgt die Beschreibung des eigentlichen Szenarios samt Tabelle mit Beispielen, welche jetzt definierte Randbedingungen aus einem vorherigen Specification Workshop sein könnten. Hierbei wird bewusst auf natürliche Sprache gesetzt und es stehen Bindings für weitere Sprachen zur Verfügung.

Nachdem die Features und Szenarien definiert wurden muss noch der entsprechende Glue Code in Form von Stepfiles ergänzt werden, einen Auszug daraus sehen wir uns hier an:

public class TodoSteps {
    @Given("I create a todo with the title {string}")
    public void given_set_title(String title) {
        this.todoBase.setTitle(title);
    }
    @And("the description {string}")
    public void and_set_description(String description) {
        this.todoBase.setDescription(description);
    }
}

Hier bieten die Java Bindings hilfreiche Annotationen, mit denen ein Matching über reguläre Ausdrücke leicht von der Hand geht.

Nach einem Testdurchlauf sieht man dann u.a. folgende Ausgabe:

Scenario Outline: Create a todo with title and description and check the id. # src/test/resources/features/todo.feature:11
  Given I create a todo with the title "title1"                              # dev.unexist.showcase.todo.domain.todo.TodoSteps.given_set_title(java.lang.String)
  And the description "description1"                                         # dev.unexist.showcase.todo.domain.todo.TodoSteps.and_set_description(java.lang.String)
  Then its id should be 1                                                    # dev.unexist.showcase.todo.domain.todo.TodoSteps.then_get_id(int)

Neben dieser reinen Textausgabe beim Build bietet Cucumber die Möglichkeit, mittels externer Tools weitere Reports zu erzeugen oder optional einen Standardreport über https://reports.cucumber.io online einzusehen:

image

image

Zusammenfassung
  1. Testfälle werden in Cucumber in natürlicher Sprache geschrieben.
  2. Es können Tabellen verwendet werden, um Testdaten für die Szenarien bereitzustellen.
  3. Abseits der Textausgabe eines Builds können weitere Reports gesondert erstellt werden.

Als nächstes Tool sehen wir uns FitNesse an, welches einen etwas anderen Weg geht.

FitNesse
Einleitung

Beim vorherigen Tool haben wir gesehen, wie mittels Tabellen und einer Domain-Specific Language Szenarien beschrieben und durch Reporting Ergebnisse auch für die Businessseite aufbereitet werden können.

FitNesse geht hierbei einen anderen Weg und bietet ebenfalls wie Cucumber datengestützte Tests an, allerdings werden diese in einer eigenen Wikiengine angezeigt und können auch dort direkt im Browser ausgeführt werden:

image

Natürlich ist es auch möglich, diese Tests headless ohne Browser in einer Pipeline auszuführen:

$ java -jar lib/fitnesse.jar -c "FrontPage?suite&suiteFilter=MustBeGreen&format=text"

Uns sollte der Testfall ja mittlerweile vertraut sein, daher springen wir direkt zum Beispiel.

Beispiel

Der Quelltext der Wikiseite sieht dann so aus:

!1 Create a todo
----
!contents -R2 -g -p -f -h
|import|
|dev.unexist.showcase.todo.domain.todo|
Create various todo entries to test the endpoint.
!|Todo Endpoint Fitnesse Fixture |
| title   | description   | id?  |
| title1  | description1  | 1    |
| title2  | description2  | 2    |

Hier wird neben Befehlen für die Wahl der Testengine und Formatierung der Inhalte, die wir jetzt außen vor lassen, zunächst der Pfad für die Imports festgelegt und anschließend werden wie auch bei Cucumber die Testdaten tabellarisch aufgeführt.

public class TodoEndpointFitnesseFixture {
    private TodoBase todoBase;
    private RequestSpecification requestSpec;
    public void setTitle(String title) {
        this.todoBase.setTitle(title);
    }
    public int id() {
        String location = given(this.requestSpec)
            .when()
                .body(this.todoBase)
                .post("/todo")
            .then()
                .statusCode(201)
            .and()
                .extract().header("location");
        return Integer.parseInt(location.substring(location.lastIndexOf("/") + 1));
    }
}

FitNesse greift im Testlauf direkt auf die Methoden des Fixtures zu: Somit wird aus der Spalte title ein Aufruf des Getters getTitle und analog dazu sorgt die Spalte id für einen Aufruf des Setters id.

Im Browser selbst kann dann mittels Klick auf Test der Test gestartet werden und die Elemente der Seite werden je nach Testergebnis dann farblich hervorgehoben:

image

Zusammenfassung
  1. FitNesse verwendet ein eigenes Wiki für die Testfälle und bietet die Möglichkeit, diese auch direkt dort auszuführen.
  2. Wie auch bei Cucumber werden Tabellen eingesetzt, um Testdaten für die Szenarien bereitzustellen.
  3. Eine Übersicht über Testläufe kann direkt im Wiki erfolgen und bietet so eine einfache Möglichkeit für die Businessseite, den Status einzusehen.

Und zuletzt widmen wir uns jetzt Concordion, welches Markdown einsetzt, um Testfälle und Reports zu erzeugen.

Concordion
Einleitung

Im Grunde sehen wir hier bekanntes Vorgehen, wie auch bei den Vorgängern ist es bei Concordion möglich, Tests in einem Dokument zu beschreiben und Daten in Form von Tabellen zu hinterlegen. Der erwähnenswerte Unterschied hier ist, es wird Markdown als Glue Code eingesetzt, um die Verbindung zwischen Testfall und Fixture herzustellen.

Das ganze ist einfacher gezeigt als erklärt, daher springen wir direkt zum Beispiel.

Beispiel
# Create a todo
This is an example specification, that demonstrates how to facilitate markdown
and [Concordion](https://concordion.org) fixtures.
### [Simple example](- "simple_example")
A todo is [created](- "#result = create(#title, #description)") with the simple
title **[test](- "#title")** and the matching description
**[test](- "#description")** and [saved](- "#result = save(#result)") as ID
[1](- "?=#result.getId").

Auf den ersten Blick mag einen das jetzt erschlagen, allerdings findet man sich mit ein wenig Markdown-Kenntnis schnell zu recht. Der interessante Teil ist hierbei die Verwendung von Links:

  • Mittels #title können in Concordion Variablen gesetzt werden, in diesem Fall die Variable title.
  • Verwendet man dabei ein Gleichheitszeichen wie bei #result = #title, erzeugt man eine Zuweisung.
  • Lässt man alles davon weg wie bei create, wird diese Methode des Fixtures aufgerufen.
  • Und beginnt man mit einem Fragezeichen wie bei ?=#result, wird ein AssertEquals aufgerufen.

Im obigen Beispiel nutzen wir wieder den ersten Testfall und wollen lediglich ein Todo mit title und description angelegen, dies sieht im Fixture dann so aus:

@RunWith(ConcordionRunner.class)
public class TodoConcordionFixture {
    public TodoBase create(final String title, final String description) {
        TodoBase base = new Todo();
        base.setTitle(title);
        base.setDescription(description);
        return base;
    }
}

Startet man den Testlauf, wird ein Report erzeugt, der in etwa so aussieht:

image

Jetzt sind die ganzen inline Links relativ komplex und werden schnell unübersichtlich, daher ist es ebenfalls möglich die alternative Schreibweise von Links in Markdown zu verwenden:

### [Simple example with different notation](- "simple_example_modified")
A todo is [created][createdCmd] with the simple title **[test](- "#title")** and
the matching description **[test](- "#description")** and [saved][savedCmd]
as ID [1](- "?=#result.getId").
[createdCmd]: - "#result = create(#title, #description)"
[savedCmd]: - "#result = save(#result)"

image

Und abschließend dazu sind natürlich ebenfalls Tabellen in Concordion möglich:

### [Simple table example](- "simple_table")
This example creates todos based on table values:
| [createAndSave][][Title][title] | [Description][description] | [ID][id] |
| ------------------------------- | -------------------------- | -------- |
| title1                          | description1               | 3        |
| title2                          | description2               | 4        |
[createAndSave]: - "#result = createAndSave(#title,#description)"
[title]: - "#title"
[description]: - "#description"
[id]: - "?=#result.getId"

Hierbei müssen dann auch lediglich die Spaltennamen mit der Logik versehen werden, sodass die Tabellen selbst relativ übersichtlich bleiben:

image

Zusammenfassung
  1. Hier ist der wesentliche Unterschied die Form, in der die Testfälle geschrieben werden.
  2. Neben Tabellen können hier auch einzelne Werte direkt eingesetzt und im Testszenario flexibel verwendet werden.
  3. Die Ausgabe des Testlaufs ist die generierte Seite, die mittels Markdown beschrieben wurde.

Fazit

Die Grundidee bei Specification by Example ist, ein Dokument zu erzeugen, welches neben reinen Testdaten auch Kontext und wichtige Randbedingungen liefert und bei Änderungen gepflegt bzw. überarbeitet wird. Diese lebende Dokumentation kann dann Testpläne und auch Tickets überdauern, sodass auch später noch verständlich ist, worum es dabei geht.

Ein weiterer wichtiger Aspekt ist der Austausch der beteiligten Rollen, die im Rahmen dieser Workshops zusammenkommen und im Vorfeld ein gemeinsames Verständnis schaffen. Die im Artikel vorgestellten Tools können zusätzlich dabei unterstützen und sowohl Ergebnisse als auch Dokumentation der Testläufe aufbereiten, sodass sie den richtigen Personen zielgruppengerecht zur Verfügung gestellt werden können.

Bild Christoph  Kappel

Autor Christoph Kappel

Christoph Kappel arbeitet als Softwarearchitekt in der LoB Cross Industries und beschäftigt sich in seiner Freizeit auch mal mit Technologie.

Diese Seite speichern. Diese Seite entfernen.