/  7
 
Zustand als Abhängigkeit -IoC konsequent gedacht
Ralf Westphal
Design for Testability ist auch Design for Flexibility.Deshalb liebe ich TDD. Allerdings bin ichdabei immer wieder über einen Problemfall gestolpert: die Prüfung des Zustands eines system under test (SUT). Als Beispiel hier ein typischer (nicht unbedingt optimaler) Ansatz bei der bekanntenund beliebtenKataPotter .Viele denken bei der Implementierung der Preisberechnung für die KataPotter an einen Warenkorb,den es zu füllen gilt, um dann den Preis unter Anwendung der Rabatte zu berechnen. Sie definiereneine solche Klasse dann z.B. so:
class KataPotterShoppingBasket{ private Dictionary<int,int> books =new Dictionary<int,int>();  public voidAddBook(intbookId) { //...} public decimalTotal { get{ //...}}}
Ein zugehörige Test scheint zunächst einfach:
[TestFixture]public class testKataPotterShoppingBasket {[Test] public voidEnthält_ein_Buch() { varsut =new KataPotterShoppingBasket();  sut.AddBook(1); //?}}
Aber, ach, ohje… Wie prüft man denn nun, ob das hinzugefügte Buch auch im Warenkorbangekommen ist? AddBook() ist ein Kommando und verändert den Zustand des Warenkorbs. Wiekann man also ganz allgemein diesen Zustand in Tests prüfen?
Zugriffsroutinen für Zustand?
 Naheliegend ist die Prüfung von Zustand durch direkten Zugriff auf den Zustand. Dafür muss dasSUT nur einen solchen Zugriff anbieten. Für einen üblichen Warenkorb mag das der Fall sein. Da
Zustand als Abhängigkeit, 26.12.2009Seite 1
 
gibt es nicht nur ein AddBook(), sondern sicher auch noch ein GetBook() oder eine ganzeCollection von Books, über die man prüfen kann, ob eine Zustandsveränderung wirksam war.Der KataPotter-Warenkorb braucht solche Zugriffsroutinen aber nicht. Warum sollten sie also nur für den Test eingeführt werden? Ich halte nichts davon, weil sie das Interface des system under testaufblähen. Sie haben keinen Wert für die Domäne, sind in der Intellisense-Liste der Eigenschaftenund Methoden aber immer sichtbar. Da hilft auch die Deklaration als intern nicht viel. Das wärefalsch verstandenes Design for Testability.Wenn Sie die KataPotter so wie oben gezeigt angehen sollten, widerstehen Sie der Versuchung,dem Warenkorb z.B. eine Count-Eigenschaft zu geben, um den Test zum Laufen zu bekommen.Solch ein Design wäre dann zwar irgendwie testbarer, aber die Flexibilität würde sich nichterhöhen. Sie hätten nicht mehr das minimal Notwendige getan. Außerdem würde der Test dannzweierlei testen. Das ist zu vermeiden! Er würde AddBook() und Count prüfen. In beiden könnten ja Fehler stecken.AddBook() und Count wären sogar gegenseitig abhängig in Bezug auf Tests. Sie könnten auchCount nicht zuerst testen, denn dazu müssten Sie Zustand aufbauen. Wäre das nicht der Fall, ist die Nutzung von mehreren SUT-Funktionalitäten in einem Test nicht so schlimm. Dann können Sie diein separaten Tests vorweg prüfen.
Ergebnis: Vermeiden Sie, spezielle Zugriffsmethoden für Zustand nur zum Zweck desTestens einzurichten. Allemal, wenn sie sie nicht wirklich isoliert testen können.
Zustandstest durch Ableitung?
Auf Zustand können Sie aber nicht nur von außen zugreifen, einfacher ist es von innen. EineAlternative zu Zugriffsmethoden wäre daher die Ableitung einer Klasse vom SUT. Deren Zustandwäre ja der des Warenkorbs. Dazu müssten Sie nur die Sichtbarkeit des Zustands ändern:
class KataPotterShoppingBasket{ 
 protected  
Dictionary<int,int> books =new Dictionary<int,int>(); ...}
Eine Ableitung kann dann “gefahrlos” eine Zugriffsmethode anbieten. Die “verunreinigt” dieDomänenklasse nicht:
class TestableShoppingBasket:KataPotterShoppingBasket { public intCount {get{return base.books.Count; } } }
Zustand als Abhängigkeit, 26.12.2009Seite 2
 
Wenn Sie im Test die abgeleitete Klasse als SUT instanzieren, haben Sie also Testbarkeit “ohneReue”:
[Test]public voidEnthält_ein_Buch() { varsut =new TestableShoppingBasket();  sut.AddBook(1); Assert.That(sut.Count,Is.EqualTo(1)); }
Mit einer Ableitung müssen Sie nicht einmal Design for Testability betreiben. Und die Flexibilitätwird auch nicht eingeschränkt. Eine scheinbar ideale Lösung, oder? Der Eingriff, private Variablenauf protected statt private zu setzen, ist minimal. Und dass dieser Ansatz an abgeschlossenen(sealed) Klassen scheitert, ist auch unbedeutend.Dennoch will mir eine Ableitung vom eigentlichen SUT nicht recht schmecken. Nein, das liegtnicht daran, dass ich Ableitungen für generell überbewertet halte. Hier wäre ja sogar dasLiskovSubstitution Principleeingehalten. Den Aufwand für die Ableitungsklasse finde ich auch nicht zuhoch. Nein, es etwas anderes, das mich stört…
Zustand als Abhängigkeit
Ich glaube, mich stört, dass Zustand hier eine Extrawurst gebraten bekommt. Zustand wird nichtseiner Natur nach behandelt. Denn Zustand ist eine Abhängigkeit!Üblicherweise denken wir bei Abhängigkeiten an Funktionalität. Der Warenkorb könnte z.B. voneiner Preisberechnungsfunktionalität abhängen:
Zustand als Abhängigkeit, 26.12.2009Seite 3

Share & Embed

More from this user

Add a Comment

Characters: ...