Behavior Driven Development! In den letzten Jahren fällt dieser Begriff immer häufiger im Zusammenhang mit Test Driven Development (TDD), welches inzwischen sehr weit verbreitet ist. Manchmal spricht man auch von Specification Driven Development (SDD), das einfach nur ein Synonym für BDD ist. Doch was genau ist eigentlich BDD?
In der agilen Softwareentwicklung ist es üblich, dass der Fachbereich - in erster Linie Stakeholder & Product Owner - neue Features für eine Anwendung konzipieren, diese spezifizieren und in sogenannten User-Stories schriftlich festhalten. Diese Stories werden anschließend priorisiert an die Entwickler übergeben, um die Anforderungen letztendlich in die Anwendung integrieren zu lassen. Oft fehlt es den Anforderungen an essenziellen Details oder sie sind aus Sicht des Entwicklers nicht vollumfänglich durchdacht.
BDD ist der Versuch, den Fachbereich des Entwicklungsteams tiefer in den Entwicklungsprozess zu integrieren, um die Qualität der Spezifikationen (User-Stories) zu verbessern. Jedes Feature wird vom Fachbereich in mehrere Szenarien zerlegt und in einer einheitlichen Form schriftlich festgehalten:
1Feature: Is it already Friday?
2 Everybody wants to know if it's Friday
3
4 Scenario: Sunday isn't Friday
5 Given today is Sunday
6 When I ask if it's already Friday
7 Then I should be told "No"
Im Unterschied zum Schreiben einer User-Story, wird durch BDD vorausgesetzt, dass Beispiele und die erwarteten Ergebnisse genauestens spezifiziert werden.
Jede dieser Spezifikationen dient dem Entwickler später als Vorlage für einen Testcase. Analog zum Test-Driven-Development werden zunächst Tests implementiert, bevor mit der Anwendungsentwicklung begonnen wird. BDD ist damit als eine Erweiterung des klassischen TDD zu sehen.
Die Anwendung gilt als vollständig funktionsfähig und den Anforderungen der Stakeholder entsprechend, sobald alle Tests erfolgreich ausgeführt wurden. Die Stakeholder haben somit direkten Einfluss auf die Qualität der Software! Je besser die Testfälle definiert werden, desto stabiler wird später die Software sein.
Im Prinzip zeigt Behavior-Driven-Development wie man richtig testet – nämlich nicht die Implementierung, sondern das Verhalten des Codes aus fachlicher Sicht. Man spricht dabei von Blackbox-Testing. Klassisches Unittesting ist in der Regel Whitebox-Testing, man orientiert sich sehr stark am Sourcecode!
In der Praxis gibt es verschiedene Tools, die das Arbeiten mit BDD vereinfachen. Das Bekannteste ist wohl Cucumber und dessen Portierungen auf verschiedene Programmiersprachen. Hierbei wird die Gherkin-Syntax verwendet, um die Testfälle in einer formalen Sprache zu definieren.
Gherkin unterteilt Testfälle in Features, Szenarien und Schritte (Steps). Ein Feature entspricht dabei einer User Story und ist eine Sammlung von Szenarien. Ein Szenario ist ein konkreter Anwendungsfall, der das Verhalten der Software anhand einer Sequenz von Schritten beschreibt. Schritte sind unterteilt in Given-, When- und Then-Ausdrücke und werden in sogenannten Feature-Files festgehalten:
1Feature: Shopping cart
2
3 Scenario: Customer clears the shopping cart
4 Given a customer puts a product into the shopping cart
5 And the customer navigates to the cart detail page
6 When he clicks on the "clear cart" button
7 Then the cart should be empty
8
9 Scenario: Test shopping cart detail page link
10 Given a customer puts a product into the shopping cart
11 And the customer navigates to the cart detail page
12 When he clicks on the "product details" button of the product
13 Then the application should navigate to the product detail page
Given beschreibt die Voraussetzungen des Tests. Das kann z.B. das Seeding von Datenbanktabellen oder das Setzen von Zuständen auf einer Website beinhalten. Die eigentliche Interaktion mit dem System findet im When-Schritt statt. Das Ergebnis dieser Interaktion wird in Then validiert.
Sobald die Feature-Files fertig definiert sind, können die Entwickler mit der technischen Umsetzung beginnen. Für jeden Ausdruck (Step) wird eine Funktion (Step-Definition) implementiert. Im nachfolgenden Beispiel sind Cypress End-to-End-Implementierungen zu sehen:
1import { Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
2
3Given('a customer puts a product into the shopping cart', () => {
4 // code for adding a item into the cart ...
5});
6
7Given('the customer navigates to the cart detail page', () => {
8 cy.visit('/shopping-cart');
9});
10
11When('he clicks on the "clear cart" button', () => {
12 cy.get('[data-e2e=clear-cart]').click();
13});
14
15Then('the cart should be empty', () => {
16 cy.get('[data-e2e=cart-items]').should('be.empty');
17});
18
19When('he clicks on the "product details" button of the product', () => {
20 cy.get('[data-e2e=to-product-details]').click();
21});
22
23Then('the application should navigate to the product detail page', () => {
24 cy.get('[data-e2e=headline]').contains('Product details')
25});
Die Beschreibungen der Steps in den Feature-Files werden eins zu eins auf die zugehörigen Funktionen gemappt. Step-Definitions können mehrfach verwendet werden. Im Beispiel “Shopping Cart” werden die beiden Given-Implementierungen in beiden Szenarien verwendet. Eine Wiederverwendung von Step-Definitions kann langfristig eine Menge Test Code einsparen.
Die meisten IDEs bieten Autovervollständigung an, um bereits existierende Step-Definitions leichter zu finden und auf fehlende Steps hinzuweisen.
Moderne Anwendungen sind meist so abstrakt und komplex, dass es wenig bis keinen Sinn macht, Kollegen und Kolleginnen des Fachbereichs in die Implementierungsdetails einzuarbeiten. Aus diesem Grund sollte vermieden werden, BDD für Unittests zu verwenden. In folgenden zwei Bereichen ist die Anwendung von BDD Tests sinnvoll:
API Tests: Wird die Schnittstelle ausschließlich vom eigenen Frontend genutzt, oder auch von Fremdapplikationen? Im ersten Fall spricht man von einem BFF (Backend for Frontend) und sollte sehr genau evaluieren, ob BDD Tests an dieser Stelle sinnvoll sind. Die API eines BFFs ist oft nicht sehr beständig. Das API-Design wird aufgrund der ausschließlichen internen Verwendung gerne vernachlässigt. Aus diesen Gründen macht es mehr Sinn sich mit dem BDD-Ansatz auf End-to-end-Tests zu fokussieren. Beim End-to-end Testing wird das interne BFF automatisch im vollen Umfang mit getestet. Wird die API von anderen Applikationen verwendet, ist sie damit das Endprodukt und BDD Tests sind essentiell. In diesem Fall muss der Fachbereich im API-Design geschult werden und ist daher auch in der Lage BDD Tests zu schreiben.
E2E (End-to-end) Tests: Verfügt die Anwendung über ein Frontend, ist die Verwendung des BDD Testings an dieser Stelle am sinnvollsten. In der Regel wird die Applikation vor jedem Release durch den Fachbereich über das Frontend getestet. Hierbei ist ein Leichtes, die manuellen Tests gleich in Feature-Files zu fassen.
In klassischen Softwareprojekten verlieren die Entwickler meist sehr viel Zeit, die genauen Spezifikationen während der Implementierung vom Fachbereich zu erfragen. Dieses hin und her ist zeitintensiv, anstrengend und kann schlimmsten Fall dazu führen, dass wichtige Testcases vergessen oder sogar falsch umgesetzt werden. Durch BDD wird versucht, hier Abhilfe zu schaffen und den Fachbereich zu einer klaren Spezifizierung aller Edge Cases zu motivieren. Diese Arbeitsweise kann, sofern sie konsequent durchgezogen wird, eine ganze Reihe von Vorteilen bringen:
In der Theorie liefert BDD nur Vorteile, wogegen es in der Praxis leider oft anders aussieht.
Projekte mit einem BDD Ansatz zu starten und zu versuchen, diesen konsequent durchzuziehen, ist eigentlich immer eine gute Entscheidung. Es ist auch kein Beinbruch, wenn man BDD nicht zu 100% umsetzen kann. In erster Linie geht es dabei nicht um die Entwickler, sondern darum, den Fachbereich zu animieren, schon vor dem Implementierungsbeginn detaillierte Gedanken über Spezifikationsdetails zu machen und diese in einheitlichem Format festzuhalten. Falls man dies auch nur ansatzweise hinbekommt, kann man den Entwicklungsprozess erheblich beschleunigen. Fachliche Designfehler der Anwendung werden frühzeitig erkannt, der Kommunikationsaufwand zwischen Fachbereich und Entwicklern wird erheblich reduziert und die Qualität der Testfälle wird verbessert. Damit wird eine stabilere Software gewährleistet.
Ein wichtiger Aspekt ist sicher auch, dass der Fachbereich ein besseres Gefühl dafür bekommt, wie viel Arbeit es wirklich ist, alle Spezifikationen bis ins kleinste Detail vorauszuplanen. Im Endeffekt ist Programmieren nichts anderes als fachliche Anforderungen in ein maschinenlesbares Format zu bringen.
Bei einer guten Spezifikationen können sich Entwickler ausschließlich auf die technische Umsetzung konzentrieren. Heutzutage ist es schwer genug, als Entwickler überhaupt auf dem neuesten Stand zu bleiben. Es sollte daher immer versucht werden, so viel Zeit wie möglich in die Implementierung zu stecken.
Wichtig ist ständig zu hinterfragen, ob der zusätzliche Aufwand von BDD in Relation zum Nutzen steht. Der Aufwand lohnt sich -meiner Erfahrung nach- nur bei größeren Projekten. Man sollte sich im Voraus Gedanken machen, ob man die richtige Teammentalität hat, um BDD auf lange Sicht durchführen zu können? Wenn der Fachbereich eigentlich gar kein Interesse an dem Thema zeigt, oder sich Programmierer bezüglich des zusätzliche Testaufwandes querstellen, sollte man es lieber lassen.
BDD funktioniert nur, wenn alle mitziehen!