Ich lese schon eine Weile bei
Alt.Net (DE) mit. In letzter Zeit war da immer mal wieder das Thema von BDD (Behaviour Driven Design) als Evolution zu TDD (Test Driven Design). Nachdem ich einige Zeilen darüber gelesen habe, war mir klar, dass das eine genauere Betrachtung verdient hat.
Nachfolgender Artikel (und folgende) sollten nicht als Experten-Essay verstanden werden - vielmehr soll es Einsteigern helfen, die die gleichen Fehlüberlegungen machen und sich in die gleichen Sackgassen verrennen wie ich. Natürlich wär's auch schön, wenn ich von dem einen oder anderen Experten einen Schubs in die richtige Richtung erhalten würde. :-)
Aber worum gehts denn nun überhaupt:
TDD, BDD... WTF?
Wie bereits gesagt, ist BDD als Evolution und nicht etwa als Alternative zu TDD zu verstehen. Kurz gesagt: TDD stellt den Test in den Mittelpunkt, BDD das Verhalten (Behaviour). Auf den ersten Blick erscheinen BDD-Tests recht speziell. Die Klassen- und Test-Namen sind in sprechender Form gehalten. Das ist dann auch gleich einer der Hauptpunkte von BDD: die Sprache. BDD-Tests sollen in einer Sprache definiert sein, die auch ein Fach-Vertreter lesen und verstehen kann.
Hands On
Hier mal ein Beispiel: eine Klasse "Television" soll getestet werden. Es wird das Verhalten getestet, wenn der Fernseher eingeschaltet wird.
Für das Beispiel wurde xUnit mit den xUnitBddExtensions von Björn Rochel verwendet. Diese beiden Frameworks erlauben es, den BDD-Gedanken bestmöglichst in Code umzusetzen.
1: [Concern(typeof (Television))]
2: public class when_Television_turns_on : InstanceContextSpecification<Television>
3: {
4: protected override Television CreateSut()
5: {
6: return new Television();
7: }
8:
9: protected override void Because()
10: {
11: Sut.TurnOn();
12: }
13:
14: [Observation]
15: public void Should_start_on_channel_one()
16: {
17: Sut.GetCurrentChannel().ShouldBeEqualTo(1);
18: }
19:
20: [Observation]
21: public void Should_allow_to_turn_off()
22: {
23: Sut.TurnOff().ShouldBeTrue();
24: }
25: }
Die Basisklasse InstanceContextSpecification<T> wird vom xUnitBddExtensions-Framework bereitgestellt.
In CreateSut (Sut = System under test), wird also die zu testende Instanz erstellt. Because stellt dann also den eigentlichen Grund des Testens dar, in diesem Falle also die TurnOn-Methode. Danach folgen zwei Observations, die eigentlichen Tests.
Wenn man sich nun die Resultate in einem Testrunner anschaut, sieht man auch, wie sprechend das ganze jetzt daher kommt:
So ein Bericht kann man gut auch jemandem von Fach vorlegen, ohne dass dieser die Stirn runzelt - ok, wenn noch alles rot ist so wie hier, vielleicht schon :-)
Was man auch schön sieht ist, dass sich dieser Bericht wie das Inhaltsverzeichnis einer Spezifikation liest. Die Tests sind also viel näher bei den Anforderungen - auch sprachlich. Mein Eindruck war auch, dass sich Tests so einfacher identifizieren lassen.
AAA
Und gleich nochmal so ein TLA ;-) AAA steht für "Arrange Act Assert" und stellt ein Syntax-Pattern dar. In unserem Beispiel wurde der Test in genau drei solche Teile zerlegt:
- CreateSut (= Arrange)
- Because (= Act)
- Should_x (= Assert)
Der Vorteil dieser Syntax ist, dass die verschiedenen Bereiche des Tests sauber voeinander getrennt sind - so können die Observations auf das wirklich Wesentliche (nämlich das Testen der Verhalten) reduziert werden.
Ansatzweise ist das auch in den bekannten TestSetup-Verfahren von nUnit & Co. umgesetzt - allerdings wird dort das Act und Assert meist vermischt. Die xUnitBddExtensions erlauben hier eine schöne Trennung und saubere Syntax.
Das war ein erster kleiner Einblick in BDD, welcher hoffentlich zum Einstieg einlädt. Ich kann es nur empfehlen. Wer damit starten möchte, dem möchte ich nochmals die xUnitBddExtensions wärmstens ans Herz legen. Die Sourcen umfassen neben dem Framework auch noch zwei Templates für Resharper, welche die Test-Erstellung erleichtert. Ausserdem wurden die Extensions selbst mit BDD-Tests getestet, das heisst man hat dort schon jede Menge guter Samples drin.