Suche
Bevor eine User Story implementiert werden darf, muss sie getestet sein!

Testend entwickeln – entwickelnd testen

Martin Uhlig, Michael Thiele, Vincent Tietz

© Shutterstock.com/ Protasov AN

Agile Teams wollen regelmäßig liefern. Und zwar nicht irgendwas, sondern qualitativ hochwertige, getestete und für Kunden wertvolle Software. Agile Teams wollen kurze Entwicklungszyklen, um frühzeitig Feedback zu bekommen. Agile Teams müssen sicher sein, dass Änderungen die bisherige Funktionalität nicht beeinträchtigt. Testgetriebene Entwicklung ist hier das Mittel der Wahl.

Je höher wir die Testpyramide klettern, umso aufwändiger und problematischer werden Formulierung und Ausführung von Tests. Werden Testszenarien vor der Implementierung vom Tester und Entwickler gemeinsam erstellt, ermöglicht das die testgetriebene Entwicklung auf allen Ebenen der Softwareentwicklung und kann beide Rollen näher zusammenzubringen.

Das Spiel vom Schwarzen Peter

Scrum bietet uns ein Rahmenwerk zur schnellen und fokussierten Umsetzung von wertstiftenden Anforderungen in selbstorganisierten Teams. Sind die User Stories erst einmal geschrieben und priorisiert, gehen Entwickler meist mit Begeisterung ans Werk, implementieren Unit-Tests, achten auf Clean Code, bestehen das Code Review, integrieren den Code und gehen motiviert zur nächsten User Story. Vermutlich fühlen sich die Entwickler höchst produktiv und sind gut gelaunt.

Doch dann kommt der Spielverderber. Der Tester hat einen Fehler entdeckt. Möglicherweise wurde ein Akzeptanzkriterium gebrochen. Entwickler müssen die Arbeit an der aktuellen User Story unterbrechen, den Fehler beheben, ein weiteres Review durchführen. Währenddessen findet der Tester weitere Fehler. Beeinflussen sich die Implementierungen gegenseitig, gibt es Fehlermaskierungen. Es kommt die Frage auf: Funktionieren die anderen Teilbereiche der Software noch? Der Schwarze Peter wird zwischen Testern und Entwicklern hin- und hergeschoben. Oder ist vielleicht doch der Analyst Schuld? Am Ende ist das Sprintziel in Gefahr.

Dabei ist das Team überzeugt davon, ein gutes Vorgehen zu haben. Testfälle werden vor der Testausführung vom Tester in ein Test-Tool geschrieben. Entwickler schauen sich eventuell die Testszenarien an und implementieren die User Story nach bestem Wissen und Gewissen. Technische Schulden sind minimal, die Abdeckung durch automatisierte Unit-Tests könnte nicht besser sein. Der Tester prüft die Akzeptanzkriterien, hat die Fehler im Blick und pflegt die Testfallsammlung. Er kann beurteilen, welche Teile regressiv nachgetestet werden müssen und wo explorative Tests notwendig sind. Das Team harmoniert.

Abb.1

Schauen wir jedoch genauer hin, machen wir eine nüchterne Feststellung: Wartezeiten, Reibungsverluste, Verschwendung – alles, was wir durch Scrum vermeiden wollen, stellen wir durch einen gut gemeinten Qualitätssicherungsprozess zur Disposition. Nach klassischer Vorgehensweise werden Tests parallel und unabhängig vom zu testenden System entwickelt oder gar erst nach der Entwicklung. Das hier beschriebene Team setzt User Stories wasserfallartig um. Gleichzeitig sind unterschiedliche Personen mit unterschiedlichem Fokus und unterschiedlichen Artefakten beteiligt. Die User Story wird über einen langen Zeitraum hinweg nicht fertig, Schuldzuweisungen können die Folge sein.

Wir stehen hier vor eigentlich zwei Problemen. Einerseits handelt es sich um ein Kommunikations- und Verständnisproblem. Nicht alle Beteiligten haben die gleiche Vorstellung von den Akzeptanzkriterien, da es meist einen Spielraum für Interpretationen gibt. Völlig natürlich vertiefen sich Entwickler in die Implementierung und setzen sich meist nur oberflächlich mit den Akzeptanzkriterien auseinander. Anderseits haben wir ein technisches Problem. Da die Akzeptanztests erst nach der Implementierung ausgeführt werden können, ist ein hoher Zeitaufwand notwendig, bis die User Story fertig getestet – also wirklich fertig – ist. Deshalb möchten wir die Tugenden der testgetriebenen Entwicklung, die heute für jeden Entwickler selbstverständlich sein sollten, auf die fachliche Ebene der Akzeptanzkriterien übertragen.

Testgetriebene Entwicklung mit Akzeptanztests

Die Vorteile liegen auf der Hand. Es gibt eine einfache Metrik, ob Anforderungen erfüllt sind oder nicht. Testgetriebene Entwicklung führt dazu, dass die Software stets an die neuen Anforderungen angepasst werden muss. Fehler sind besser lokalisierbar, es kann ohne viel Zeitaufwand geprüft werden, ob die Software funktioniert. Das gesamte Team ist dadurch offener für Veränderungen. Nicht zuletzt dokumentieren die Tests die Funktionalität der Software. Es lohnt sich deshalb, die testgetrieben Entwicklung auf die Ebene der Akzeptanztests anzuwenden. Aus unserer Sicht bedarf es der folgenden Prinzipien.

Akzeptanzkriterien formulieren

Der Kunde hat eine Idee für ein Feature im Kopf und eventuell ein Szenario, wie sich die zu entwickelnde Software verhalten soll. Der Product Owner nimmt dieses Szenario auf und formuliert auf dessen Basis eine oder mehrere User Stories, da das geforderte Feature meist sehr umfangreich ist. Hierbei wird jede User Story um Akzeptanzkriterien erweitert. Diese definieren in kurzer und knapper Form gewünschte und messbare Kriterien, damit die Story als „Done“ angesehen werden kann. Anhand der Akzeptanzkriterien können alle Beteiligten gut über die Story diskutieren, sie erweitern, fachliche Fragen stellen, technische Implikationen diskutieren und – nach Fertigstellung – auch abnehmen.

Gemeinsam Testszenarien entwickeln

Testszenarien dienen der Überprüfung der Akzeptanzkriterien einer User Story. Das Entwickeln von Akzeptenztests sollte deshalb ebenso wie das Entwickeln von User Stories als Kommunikationswerkzeug für alle Rollen im Scrum-Team genutzt werden. So können alle das gleiche Verständnis über das Zielverhalten bekommen. Bewährt hat sich bei uns, dass sich vor der Implementierung einer User Story  mindestens ein Entwickler und ein Tester zusammensetzen und die Testszenarien gemeinsam entwickeln. Für Rückfragen sollte der Product Owner zu Verfügung stehen. Das bedient auch gleich den Aspekt des Vier-Augen-Prinzips. Fehler in den Anforderungen oder Missverständnisse können frühzeitig erkannt werden. Weiterhin wissen Entwickler vor der Implementierung, was eigentlich umgesetzt werden soll und können Fehler in der Implementierung besser vermeiden.

Entwickler und Tester sollten unbedingt ein gleiches Verständnis der Anforderungen haben. Vorgehensmodelle wie Behaviour-Driven-Development (BDD) [1] sehen hier den Schreiber der User Story in der Pflicht, die Testszenarien für eine Story zu entwickeln. Formale Sprachen wie Gherkin sollen helfen, diese möglichst gut in ausführbaren Code zu übersetzen, hierfür ist ein Übersetzungsschritt mithilfe eines Frameworks wie beispielsweise Cucumber nötig. Für unser Team ist es aber viel wichtiger, dass Entwickler die Szenarien (mit-)schreiben und verstehen. Können wir dann nicht die Tests gleich im Code formulieren? Die Einbeziehung des Testers sichert dabei den Entwickler gegen negative Akzeptanztests ab und gibt dem Tester Einblick und Sicherheit darüber, welche automatisierten Tests bereits entwicklungsbegleitend entstanden sind. Schließlich ist es aus unserer Sicht unnötig, Testszenarien schon weit im Vorfeld zu definieren. Wir kümmern uns erst darum, wenn die User Story für die Implementierung ansteht.

Akzeptanztests im Quelltext definieren

Die Herausforderung für Entwickler ist an dieser Stelle das Umdenken: statt Bottom-Up werden Testszenarien Top-down entwickelt. Was heißt das? Statt kleine Teile zu einem großen Ganzen zu kombinieren, fängt man beim Ganzen an – einem Akzeptanzkriterium bzw. einem Szenario hierfür –  und verfeinert es schrittweise in kleinere Bestandteile. Gerade bei der Einführung von Akzeptanztest-getriebener Entwicklung ist es bei diesem Schritt unabdingbar, dass Tester und Entwickler zusammenarbeiten, denn für Tester ist dieses Vorgehen intuitiv. Wir zeigen das Vorgehen weiter unten anhand eines Beispiels.

So viel wie möglich, so wenig wie nötig automatisieren

Wenn nun alle Akzeptanztests bereits im Code definiert sind, besteht die Möglichkeit, diesen Code auch ausführen zu lassen. Hier beginnt die Kunst des automatisierten Testens auf der Ebene der Fachlichkeit, also der Akzeptanzkriterien. Wenn man sich gleich zu Beginn Gedanken darüber macht, wie man die Software, zum Beispiel durch automatisierte Oberflächentests, testen kann, so hat man die Möglichkeit, per Knopfdruck die einmal in Tests definierte Funktionalität immer wieder auszuführen, z. B. bei jedem Check-In oder im Nightly Build. Umfangreiche automatisierte Regressionstests entstehen also direkt während der Entwicklung. Hierfür muss die nötige Infrastruktur bereitgestellt werden. Zu Beginn eines Projekts fällt das wesentlich leichter, da die Testinfrastruktur mit dem Umfang der Software wächst. Schließlich sei noch erwähnt, dass nicht immer alles automatisiert getestet werden kann und auch hier Aufwand und Nutzen abgewogen werden sollten. Die Implementierung der Akzeptanztests bedeutet Mehraufwand, der sich aber im Verlaufe eines Projekts auszahlt.

Abb2

Manuelle Tests

Trotz aller Testautomatisierung ist das manuelle Testen nicht ersetzbar. Ob die Software auch von einem Menschen verstanden und bedient werden kann, ist wahrscheinlich unmöglich durch automatisierte Tests zu beweisen. Hier ist das Auge des Testers gefragt. Hinzu kommen nichtfunktionale Qualitätsmerkmale, die neben den Akzeptanzkriterien ebenfalls getestet werden müssen. Die Automatisierung der Akzeptanztests verschafft uns dafür aber mehr Zeit. Im manuellen Test kann so verstärkt auf wichtige, aber oft vom Test vernachlässigte Merkmale wie Stabilität und Fehlertoleranz geachtet werden. Kritische Bereiche oder Funktionalitäten können intensiver geprüft werden und nicht zuletzt existiert mehr Zeit für explorative Tests aller Art.

Ein Beispiel

Was macht nun ein Testszenario aus? Schauen wir uns ein kleines Beispiel an. Eine User Story hat folgendes Akzeptanzkriterium: Für Produkte existiert ein Nettopreis und ein Mehrwertsteuersatz; dem Nutzer wird der Bruttopreis angezeigt. Im ersten Schritt überlegt man sich nun einen passenden Namen, der das Szenario möglichst knapp beschreibt.

Methodenrümpfe erstellen

In diesem Fall könnte dieser lauten „Preise werden Brutto angezeigt“. Im Code könnte das nun folgendermaßen aussehen:

public PreisanzeigeST {
  @Test
  public void preiseWerdenBruttoAngezeigt() {
  }
}

Hier passiert eine Menge. Zunächst wird ein Klassenname vergeben, der fachlich mehrere Szenarien zusammenfassen kann. Die Endung ST signalisiert, dass es sich um einen SzenarioTest handelt – eine Namenskonvention, die sich zur Unterscheidung von Tests auf verschiedenen Teststufen eignet. Als nächstes folgt die Deklaration einer Testmethode, deren Implementierung zunächst leer bleibt. Die Annotation @Test sorgt nur dafür, dass JUnit bei der Ausführung der automatisierten Tests diese Methode als Test erkennt.

Vorbedingungen definieren

Als nächstes macht man sich Gedanken über Vorbedingungen wie die Existenz bestimmter Daten oder das Laufen von Services. Vorbedingungen werden nun ebenfalls zu Methodennamen. In unserem Beispiel entscheiden wir uns dazu, zwei konkrete Vorbedingungen zu definieren: der Bruttopreis soll 40 € sein und die Mehrwertsteuer 19%.

  
@Test
  public void preiseWerdenBruttoAngezeigt() {
    bruttoPreisIst40Euro();
    mehrwertsteuerIst19Prozent();
  }
  public void bruttoPreisIst40Euro() {
  }
  public void mehrwertsteuerIst19Prozent() {
  }

Wieder werden nur Methodenrümpfe erstellt. Diese werden von dem Szenario referenziert, also aufgerufen. Während die ersten Entwickler jetzt die Hände über dem Kopf zusammenschlagen, weil hier feste Werte in Methoden gecoded werden, ist dieser Schritt dennoch erst einmal wichtig, da er fachlich genau beschreibt, was gebraucht wird. Refaktorierungen wie public void bruttoPreisInEuro(double preis) können später, wenn diese Methode mehrfach gebraucht wird, noch nachträglich vorgenommen werden.

Szenario ausführen

Es folgt die Szenarioausführung, d. h. Schritte, die ausgeführt werden müssen, um das gewünschte Ergebnis zu erzielen. Wieder ergänzen wir nur fachlich benannte Methoden.

  @Test
  public void preiseWerdenBruttoAngezeigt() {
    bruttoPreisIst40Euro();
    mehrwertsteuerIst19Prozent();
    
    berechnePreis();
  }
  …
  public void berechnePreis() {
  }

Schließlich müssen die gewünschten Reaktionen sichergestellt werden, welche die Nachbedingungen eines Szenarios sind. Wir ahnen es schon: fachlich benannte Methodenrümpfe.

  @Test
  public void preiseWerdenBruttoAngezeigt() {
    bruttoPreisIst40Euro();
    mehrwertsteuerIst19Prozent();
    
    berechnePreis();

    gesamtPreisIst47_60Euro();
  }
  …
  public void gesamtPreisIst47_60Euro() {
  }

An dieser Stelle ist das Testszenario vollständig. Es soll und darf uns nicht interessieren, woher der Nettopreis kommt, mit welchem Service man den Bruttopreis berechnet, etc. Es ist auch völlig offen gelassen, ob das zu einem Unit-, Integrations- oder auch GUI-Test wird. Später, wenn die Methodenrümpfe ausimplementiert werden, kann man sich für eine oder auch mehrere Varianten entscheiden.
Das Akzeptanzkriterium kann nun in weitere Szenarien zerlegt werden. Was passiert, wenn kein Nettopreis bzw. keine Mehrwertsteuer vorhanden ist? Was, wenn der Bruttopreis „0 Euro“ ist? Soll dann evtl. die Zeichenkette „kostenlos“ angezeigt werden? All diese Szenarien werden von einem Tester vorgeschlagen und als Entwickler kann man sich schon vor der Implementierung mit Sonderfällen vertraut machen.

Fazit

Akzeptanztest im Quellcode zu beschreiben hat drei entscheidende Vorteile. Zunächst wird kein weiteres Tool benötigt, um Testszenarien zu verwalten. Die Wahrheit ist und bleibt der Quelltext, welcher automatisch versioniert ist. Hat man ein Ticketmanagementsystem welches mit der Sourcecode-Verwaltung verknüpft ist, kann auch eine Verbindung zwischen Quelltext bzw. Test und der eigentlichen Anforderung (z. B. einer User Story) hergestellt werden. Zweitens sind die Testszenarien genauso flexibel hinsichtlich der Wiederverwendung, Modularisierung und Refaktorisierung. Gleichzeitig ist hier die Akzeptanz der Entwickler höher und fördert wiederum die Flexibilität der Gesamtarchitektur. Einigt man sich auf eine Syntax, so können durch lesbare Klassen und Methodennamen Akzeptanztestes so implementiert werden, dass sie auch von einem Nicht-Entwickler geschrieben und gelesen werden können. Drittens werden für die Testautomatisierung die entscheidenden Grundlagen gelegt.
Es hört sich zunächst unmöglich an: „Bevor eine User Story implementiert werden darf, muss sie getestet sein!“ Dennoch hat dieser Satz bei uns zu einem radikalen Umdenken geführt. So entsteht die Testautomatisierung ganz nebenbei als Produkt der eigentlichen Story-Umsetzung. Ergänzt um gezielt manuelle Testmethoden kann so eine gleichbleibende Qualität über den gesamten Sprint erreicht werden. Schließlich reduziert dieses Vorgehen die Barrieren im Team, da Entwickler und Tester nicht mehr gegeneinander, sondern miteinander arbeiten. Dem „shippable increment“ am Ende eines jeden Sprints steht so (fast) nichts mehr im Weg.

Geschrieben von
Martin Uhlig
Martin Uhlig
Martin Uhlig arbeitet als Softwaretester für die Saxonia Systems AG in Dresden. Als Berater ist er in verschiedenen Projekten der Bereiche Logistik, Produktentwicklung und Automobilindustrie tätig. Bereits als Diplomand hat er sich mit Qualitätsmetriken im agilen Umfeld beschäftigt. Diese Erfahrungen konnte er auch als Tester und Product Owner in agilen Projekten einbringen und vertiefen.
Michael Thiele
Michael Thiele
Michael Thiele arbeitet als Senior Software Consultant für die Saxonia Systems AG in Dresden. In Projekten nutzt er intensiv JavaFX, das seine Liebe zur GUI Programmierung wieder geweckt hat. Seine Interessen liegen zudem in den Bereichen agile Softwareentwicklung, Testen und (funktionale) reaktive Programmierung.
Vincent Tietz
Vincent Tietz
Dr. Vincent Tietz ist Scrum Master und Senior Consultant bei der Saxonia Systems AG in Dresden. Er studierte und promovierte an der TU Dresden und arbeitete anschließend als Entwickler und Architekt in einem verteilten Scrum-Team. Gemeinsam mit seinen Kollegen entwickelt er das Konzept ETEO für agile und verteilte Zusammenarbeit. Zu diesem Thema ist er Autor, Trainer und Speaker.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.