| Eigentlich sollten Tests nicht komplex sein. Sie sollen insbesondere möglichst fehlerfrei sein. Deshalb enthalten Tests eigentlich keine Logik. Außerdem müssen sie reproduzierbar sein. Arbeitet ein Test mit Zufallszahlen und schlägt manchmal fehl, manchmal aber auch nicht, hilft dies nicht beim Debuggen. Es gibt aber ein paar Punkte, die ich unter dieser Überschrift kurz ansprechen möchte: Exceptions testens Möchte man sicherstellen, dass eine Methode eine Exception wirft, muss man im klassischen Ansatz einen kleinen Umweg laufen: [TestMethod]
public void testExceptionOnEmptyStack()
{
try
{
stack.Pop();
Assert.Fail();
}
catch (StackIsEmptyException e)
{
}
} Listing 11: testExceptionOnEmptyStack() Der Ansatz ist flexibel, das .NET Framework stellt allerdings eine kürzere Möglichkeit zur Verfügung, welche etwas weniger flexibel ist, in 90% der Fälle aber ausreicht: [TestMethod, ExpectedException(typeof(StackIsEmptyException))]
public void testExceptionOnEmptyStack()
{
stack.Pop();
} Listing 12: TestMethod mit Exception-Unterstützung ToDo-Listen Ein weiteres Szenario, welchem man häufig begegnet, ist die Problematik, dass man während einem Test merkt, dass die Implementierung komplexer wird als gedacht. Ein Beispiel ist hier die häufig zitierte Bruch-Klasse. Wir gehen davon aus, dass eine Klasse eine rationale Zahl in Form eines Bruches darstellt. Es sind bereits Nenner und Zähler implementiert. Nun soll die Addition zweier Brüche erfolgen. Wir stellen jedoch sehr schnell fest, dass wir dafür Brüche erweitern und kürzen können müssen. Für letzteres müssen wir den größten gemeinsamen Teiler von zwei Zahlen ermitteln können. Dies ist für einen Test viel zu viel. An dieser Stelle greifen wir zu einem klassischen Stück Papier. Wer mag, darf auch Notepad verwenden, aber die meisten schwören hier darauf, selbst herumkritzeln zu dürfen. Wir notieren "ggt", "kuerzen" und "erweitern(int)" oder ähnliches und kommentieren unseren angefangenen Test aus. Nun wird die ToDo-Liste abgearbeitet. Ist sie leer, kann mit der Addition von zwei Brüchen fortgesetzt werden. Die Idee ist, dass immer nur eine Baustelle offen sein sollte. Das bedeutet, dass immer nur der neu geschriebene Test rot sein darf. Wird es unübersichtlich, haben wir nicht viel gewonnen. Es ist völlig in Ordnung, abzubrechen und sich von der anderen Seite zu nähern. Test-First bedeutet nicht, Top-Down (Bruch, Addition, Kuerzen, GGT) oder Bottom-Up (GGT, Kuerzen, Addition, Bruch) zu entwickeln. Bei TDD entwickelt man vom Bekannten ins Unbekannte. Dabei ist es legitim, zwischendurch die Richtung zu wechseln. Sprechende Tests Schlägt eine Test-Methode fehl, und der Name lautet testOneAndOneIsTwo, lässt sich bereits erahnen, was getestet wird. Lautet der Text des Fehlschlages Assert.AreEqual failed. Expected:<1>. Actual:<2>., sagt dies eigentlich alles. Tests sollten kurz sein. Sind ihre Namen sprechend gehalten zeigt ein Fehlschlag schnell, wo in etwa das Problem zu suchen ist. Die Assert-Klasse bietet die Möglichkeit, die einzelnen Assertions mit Kommentaren zu versehen. Dies kann genutzt werden, wenn die normale Ausgabe nicht ausreicht. Häufig wird in diesem Fall allerdings gelästert, warum es sich nicht anders lösen ließ. Der Code dazu lautet dann beispielsweise: Assert.AreEqual(expected, actual, "One and one should be two");. Die zugehörige Fehlermeldung ist jetzt Assert.AreEqual failed. Expected:<2>. Actual:<1>. One and one should be two Die Methoden der Assert-Klasse Die Klasse Assert mit ihren statischen Methoden wurde bereits mehrfach verwendet. Der Vollständigkeit halber sollen hier dennoch kurz die wichtigsten Methoden vorgestellt werden: AreEqual() testet zwei beliebige Werte auf Gleichheit. Dabei wird == verwendet. Es existieren zahlreiche Überladungen für Strings, Integer, Object, etc. Es ist auch eine generische Methode unter ihnen zu finden, welche der Object-Variante vorzuziehen ist. In der Variante für Float und Double kann zusätzlich eine Genauigkeit angegeben werden, so dass Rundungsfehler ebenfalls abgedeckt werden können. AreNotEqual() arbeitet identisch zu AreEqual(), testet aber das Gegenteil. AreSame() prüft nicht, ob die Objekte gleich im Sinne des ==-Operators sind, sondern ob es sich tatsächlich um die selbe Instanz handelt. Hierzu wird is verwendet. Analog dazu ist AreNotSame() zu betrachten. IsNull und IsNotNull prüfen, wie der Name bereits vermuten lässt, ob der übergebene Parameter null ist. IsTrue und IsFalse bekommen einen booleschen Ausdruck und prüfen diesen auf den jeweiligen Wert. Grundsätzlich ist ein Assert.AreEqual(expected, actual) einem Assert.IsTrue(expected == actual) vorzuziehen, da die erste Variante bessere Fehlermeldungen liefert. |