Vor kurzem habe ich eine Factory-Methode getestet. Die Methode bestand darin, entweder ein einfaches Objekt oder ein Objekt in einem Dekorateur zu erstellen. Das dekorierte Objekt kann von einer von mehreren Arten sein, die alle StrategyClass erweitern.
In meinem Test wollte ich überprüfen, ob die Klasse des zurückgegebenen Objekts den Erwartungen entspricht. Das ist einfach, wenn normale Objekte zurückgegeben werden, aber was ist zu tun, wenn sie in einem Dekorateur verpackt sind?
Ich codiere in PHP, damit ich ext/Reflection
eine Klasse von umschlossenen Objekten finden kann, aber es schien mir, dass es überkomplizierte Dinge sind und den TDD-Regeln in gewisser Weise widersprechen.
Stattdessen habe ich beschlossen, ein getClassName()
Objekt einzuführen , das den Klassennamen des Objekts zurückgibt, wenn es von StrategyClass aufgerufen wird. Wenn es jedoch vom Dekorator aufgerufen wird, gibt es den Wert zurück, der von derselben Methode in dem dekorierten Objekt zurückgegeben wird.
Code zur Verdeutlichung:
interface StrategyInterface {
public function getClassName();
}
abstract class StrategyClass implements StrategyInterface {
public function getClassName() {
return \get_class($this);
}
}
abstract class StrategyDecorator implements StrategyInterface {
private $decorated;
public function __construct(StrategyClass $decorated) {
$this->decorated = $decorated;
}
public function getClassName() {
return $this->decorated->getClassName();
}
}
Und ein PHPUnit-Test
/**
* @dataProvider providerForTestGetStrategy
* @param array $arguments
* @param string $expected
*/
public function testGetStrategy($arguments, $expected) {
$this->assertEquals(
__NAMESPACE__.'\\'.$expected,
$this->object->getStrategy($arguments)->getClassName()
)
}
//below there's another test to check if proper decorator is being used
Mein Punkt hier ist: Ist es in Ordnung, solche Methoden einzuführen, die keinen anderen Nutzen haben, als Unit-Tests einfacher zu machen? Irgendwie fühlt es sich für mich nicht richtig an.
Antworten:
Mein Gedanke ist nein, Sie sollten nichts tun, nur weil es für die Testbarkeit benötigt wird. Viele Entscheidungen, die von Menschen getroffen werden, wirken sich positiv auf die Testbarkeit aus, und die Testbarkeit kann sogar der Hauptvorteil sein, sollte jedoch eine gute Entwurfsentscheidung in Bezug auf andere Aspekte sein. Dies bedeutet, dass einige gewünschte Eigenschaften nicht testbar sind. Ein anderes Beispiel ist, wenn Sie wissen möchten, wie effizient eine Routine ist, zum Beispiel, wenn Ihre Hashmap einen gleichmäßig verteilten Bereich von Hash-Werten verwendet - nichts in der externen Schnittstelle kann Ihnen das sagen.
Anstatt zu überlegen, ob ich die richtige Strategieklasse erhalte, überlege ich, ob die Klasse, die ich erhalte, das leistet, was diese Spezifikation zu testen versucht. Es ist schön, wenn Sie die internen Wasserleitungen testen können, aber nicht müssen. Drehen Sie einfach den Knopf und sehen Sie, ob Sie heißes oder kaltes Wasser bekommen.
quelle
Ich gehe davon aus, dass Sie manchmal Ihren Quellcode ein wenig überarbeiten müssen, um ihn testbarer zu machen. Es ist nicht ideal und sollte keine Entschuldigung dafür sein, die Benutzeroberfläche mit Funktionen zu überfrachten, die nur zum Testen verwendet werden sollen. Daher ist Moderation hier im Allgemeinen der Schlüssel. Sie möchten auch nicht in der Situation sein, dass die Benutzer Ihres Codes plötzlich die Testschnittstellenfunktionen für normale Interaktionen mit Ihrem Objekt verwenden.
Mein bevorzugter Weg, um damit umzugehen (und ich muss mich entschuldigen, dass ich nicht zeigen kann, wie das in PHP gemacht wird, da ich hauptsächlich in C-Stil-Sprachen programmiere), ist, die 'Test'-Funktionen auf eine Weise bereitzustellen, die sie nicht sind der Außenwelt durch das Objekt selbst ausgesetzt, kann aber von abgeleiteten Objekten zugegriffen werden. Zu Testzwecken würde ich dann eine Klasse ableiten, die die Interaktion mit dem Objekt handhabt, das ich tatsächlich testen möchte, und den Komponententest veranlassen, dieses bestimmte Objekt zu verwenden. Ein C ++ - Beispiel würde ungefähr so aussehen:
Produktionsartenklasse:
Testhelferklasse:
Auf diese Weise sind Sie zumindest in der Lage, die Funktionen des Typs "Test" in Ihrem Hauptobjekt nicht offen zu legen.
quelle
Als ich vor ein paar Monaten meinen neu gekauften Geschirrspüler aufstellte, kam eine Menge Wasser aus dem Schlauch, und mir wurde klar, dass dies wahrscheinlich darauf zurückzuführen ist, dass er in der Fabrik, aus der er kam, ordnungsgemäß getestet wurde. Es ist nicht ungewöhnlich, Befestigungslöcher und ähnliches an Maschinen zu sehen, die nur zum Testen in einer Montagelinie vorhanden sind.
Testen ist wichtig, wenn nötig, fügen Sie einfach etwas hinzu.
Aber probieren Sie einige der Alternativen aus. Ihre reflexionsbasierte Option ist gar nicht so schlecht. Sie könnten einen geschützten virtuellen Zugriffsmechanismus für das haben, was Sie benötigen, und eine abgeleitete Klasse zum Testen und Bestätigen erstellen. Vielleicht können Sie Ihre Klasse aufteilen und eine resultierende Blattklasse direkt testen. Das Ausblenden der Testmethode mit einer Compiler-Variablen in Ihrem Quellcode ist ebenfalls eine Option (ich kenne PHP kaum, nicht sicher, ob das in PHP möglich ist).
In Ihrem Kontext können Sie entscheiden, nicht die richtige Zusammensetzung innerhalb des Dekorateurs zu testen, sondern das erwartete Verhalten der Dekoration zu testen. Dies würde den Fokus vielleicht etwas mehr auf das erwartete Systemverhalten richten und weniger auf die technischen Spezifikationen (was bringt Ihnen das Dekorationsmuster aus funktionaler Sicht?).
quelle
Ich bin ein absoluter TDD-Neuling, aber es scheint von der hinzugefügten Methode abzuhängen. Soweit ich TDD verstehe, sollen Ihre Tests die Erstellung Ihrer API bis zu einem gewissen Grad vorantreiben.
Wenn es in Ordnung ist:
Wenn es nicht in Ordnung ist:
quelle