b2aat6n
b2aat6n
b2aat6n
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Stack und Queue implementieren<br />
Der Nächste bitte!<br />
Immer hübsch der Reihe nach: Das gilt nicht nur im Wartezimmer, sondern auch im Stack und in der Queue der<br />
Informatiker. Und wer sich das Entwicklerleben vereinfachen will, sollte auch bei ihrer Implementierung<br />
die richtige Reihenfolge einhalten: Erst planen, dann Tests entwickeln, dann implementieren.<br />
A<br />
ls Entwickler nehmen wir<br />
Stack und Queue, wie viele<br />
andere Datenstrukturen, als<br />
selbstverständlich hin, sind<br />
sie doch im .NET Framework enthalten.<br />
Weil ihre Funktionsweise so einfach ist, besteht<br />
sicherlich die Versuchung, direkt mit<br />
der Implementierung zu beginnen. Doch<br />
schon kommen die ersten Fragen um die<br />
Ecke: Wie soll der erste Test aussehen? Wie<br />
testet man einen Stack überhaupt, das<br />
heißt, kann man Push isoliert testen? Oder<br />
kann man Push nur testen, indem man Pop<br />
ebenfalls testet?<br />
Bevor Sie versuchen, diese Fragen zu beantworten,<br />
sollten Sie den Konsolendeckel<br />
besser erst mal schließen und zu einem<br />
Stück Papier greifen. Denn die zentrale Frage<br />
vor dem ersten Test lautet, wie denn die<br />
interne Repräsentation des Stacks überhaupt<br />
aussieht. Dies mit Papier und Bleistift<br />
zu planen ist einfacher, als es einfach<br />
so im Kopf zu tun. Dabei übersieht man<br />
schnell mal ein Detail.<br />
Ein Stack muss in der Lage sein, jeweils<br />
das oberste Element zu liefern. Das ist die<br />
Aufgabe der Pop-Methode. Nachdem das<br />
oberste Element geliefert wurde, muss der<br />
Stack beim nächsten Mal das nächste Element<br />
liefern, das also unmittelbar auf das<br />
oberste folgt. Daraus ergibt sich die Notwendigkeit,<br />
innerhalb des Stacks jeweils zu<br />
wissen, welches das oberste Element ist.<br />
Ferner muss zu jedem Element bekannt<br />
sein, welches das nächste Element ist. So ergibt<br />
sich eine ganz einfache interne Datenstruktur<br />
für den Stack, siehe Abbildung 1.<br />
Hat man diese Datenstruktur erst einmal<br />
aufgemalt, ist es ein Leichtes, sie in Code<br />
zu übersetzen. Aber Achtung, den Test<br />
nicht vergessen! Bei einem Stack bietet es<br />
sich an, zwei unterschiedliche Zustände zu<br />
betrachten:<br />
❚ einen leeren Stack,<br />
❚ einen nicht leeren Stack.<br />
In solchen Fällen erstelle ich für die unterschiedlichen<br />
Szenarien gerne getrennte<br />
[Abb. 1] top- und<br />
next-Zeiger im<br />
Testklassen. Dann kann nämlich das Setup<br />
des Tests dafür sorgen, dass das Szenario<br />
bereitgestellt wird. Die Testklasse, welche<br />
sich mit einem leeren Stack befasst, instanziert<br />
einfach einen neuen Stack. In der Testklasse<br />
zum Szenario „nicht leerer Stack“<br />
wird der Stack im Setup gleich mit ein paar<br />
Werten befüllt. So können sich die Tests jeweils<br />
auf das konkrete Szenario beziehen.<br />
Doch wie sieht nun der erste Test aus?<br />
Ich habe mich entschieden, den ersten Test<br />
zu einem leeren Stack zu erstellen. Es erscheint<br />
mir nicht sinnvoll, bei dem Szenario<br />
eines nicht leeren Stacks zu beginnen,<br />
weil dann vermutlich für den ersten Test<br />
bereits sehr viel Funktionalität implementiert<br />
werden muss. Ich möchte lieber in<br />
kleinen Schritten vorgehen, um sicher zu<br />
sein, dass ich wirklich nur gerade so viel<br />
Code schreibe, dass ein weiterer Test erfolgreich<br />
verläuft.<br />
Für den ersten Test zu einem leeren Stack<br />
überlege ich mir, wie die interne Repräsentation<br />
eines leeren Stacks aussehen soll, und<br />
komme zu dem Schluss, dass der Zeiger, der<br />
auf das erste Element verweist, null sein soll.<br />
Listing 1 zeigt den ersten Test. In der Setup-<br />
Methode der Testklasse wird ein leerer Stack<br />
für int-Elemente instanziert. Der erste Test<br />
prüft, ob dann der top-Zeiger gleich null ist.<br />
Nun wird vielleicht dem einen oder anderen<br />
der Einwand im Kopf herumkreisen,<br />
dass damit ja erstens die Sichtbarkeit der<br />
internen Repräsentation nach außen getragen<br />
wird und zweitens Interna getestet<br />
werden. Zur Sichtbarkeit sei gesagt, dass<br />
Listing 1<br />
LÖSUNG<br />
Einen leeren Stack testen.<br />
[TestFixture]<br />
public class Ein_leerer_Stack {<br />
private Stack sut;<br />
[SetUp]<br />
public void Setup() {<br />
sut = new Stack();<br />
}<br />
[Test]<br />
public void Hat_kein_top_Element() {<br />
Assert.That(sut.top, Is.Null);<br />
}<br />
}<br />
ich das Feld top auf internal setze. Damit<br />
ist es zunächst nur innerhalb der Assembly<br />
sichtbar, in der die Stack-Implementierung<br />
liegt. Durch ein Attribut in der Datei AssemblyInfo.cs<br />
wird die Sichtbarkeit dann<br />
dosiert erweitert auf die Testassembly. Das<br />
Attribut sieht wie folgt aus:<br />
[assembly:<br />
InternalsVisibleTo("stack.tests")]<br />
Damit kann nun auch aus der Testassembly<br />
auf die Interna zugegriffen werden.<br />
Und schon sind wir beim zweiten Einwand,<br />
dass nun diese Interna im Test verwendet<br />
werden. Das halte ich für vernachlässigbar.<br />
Sicher ist es erstrebenswert, Tests<br />
so zu schreiben, dass sie möglichst wenige<br />
Abhängigkeiten zu den Interna der Klasse<br />
haben. Denn wenn nur über die öffentliche<br />
Schnittstelle getestet wird, sind die Tests<br />
www.dotnetpro.de dotnetpro.dojos.2011 39<br />
Stack.<br />
Listing 2<br />
Datenstruktur für die Stack-<br />
Elemente.<br />
internal class Element {<br />
public TData Data { get; set; }<br />
public Element Next{get; set;}<br />
}