Читать книгу Dojos für Entwickler - Stefan Lieser - Страница 32

Generatoren

Оглавление

Ein Generator ist für das Erzeugen von Werten eines bestimmten Typs zuständig. Welche Strategie dabei verfolgt wird, ist Sache des Generators. Dies soll am Beispiel eines Generators für int-Werte gezeigt werden, der zufällige Werte innerhalb vorgegebener Minimum- und Maximumwerte erzeugt.

Die Implementierung des Generators ist ganz einfach. Ich verwende einen Zufallszahlengenerator System.Random aus dem .NET Framework und weise ihn an, einen Wert innerhalb der definierten Grenzen zu liefern, siehe Listing 6. Die spannende Frage ist nun: Wie kann man einen solchen Generator testen, der zufällige Werte liefern soll? Sie werden bemerkt haben, dass oben im Listing der Zufallszahlengenerator random nirgendwo instanziert und zugewiesen wird. Dies liegt in der Notwendigkeit begründet, den Generator automatisiert testen zu können. Würde der Generator den Zufallszahlengenerator selbst instanzieren, würde er immer zufällige Werte liefern. Dies soll er natürlich tun, aber im Test benötigen wir die Kontrolle darüber, welche Werte „zufällig" geliefert werden, siehe Listing 7.

Listing 6: Ein Generator für int-Werte.

public class RandomIntGenerator : IGenerator<object>

{

private readonly int minimum;

private readonly int maximum;

private readonly Random random;


...


public object GenerateValue() {

return random.Next(minimum, maximum + 1);

}

}

Listing 7: Den Zufallsgenerator testen.

[TestFixture]

public class RandomIntGeneratorTests

{

private RandomIntGenerator sut;


[SetUp]

public void Setup() {

sut = new RandomIntGenerator(1, 5, new Random(0));

}


[Test]

public void Zufaellige_Werte_zwischen_Minimum_und_Maximum_werden_geliefert() {

Assert.That(sut.GenerateValue(), Is.EqualTo(4));

Assert.That(sut.GenerateValue(), Is.EqualTo(5));


...


}

}

Die Testmethode ist hier verkürzt dargestellt. Ich rufe im Test so lange Werte ab, bis alle möglichen Werte innerhalb von Minimum und Maximum mindestens einmal geliefert wurden.

Der Trick, dass sich der Random-Generator immer gleich verhält, liegt darin, dass ich ihn im Test immer mit demselben Startwert (Seed) 0 instanziere.

Um das zu ermöglichen, habe ich einen internal-Konstruktor ergänzt, der nur im Test verwendet wird, um den Random-Ge-nerator in die Klasse zu injizieren. Der öffentliche Konstruktor der Klasse instan-ziert den Random-Generator ohne Seed, sodass dieser zufällige Werte liefert, siehe Listing 8.

Listing 8: Zufallsgenerator für int-Werte.

public RandomIntGenerator(int minimum, int maximum)

: this(minimum, maximum, new Random()) {

}

internal RandomIntGenerator(int minimum, int maximum, Random random) {

this.minimum = minimum;

this.maximum = maximum;

this.random = random;

}

Bei Konstruktoren sollte man übrigens generell das Highlander-Prinzip beachten: Es kann nur einen geben (eine Anspielung auf den Film Highlander-Es kann nur einen geben). Der interne Konstruktor ist derjenige, der die eigentliche Arbeit verrichtet. Der öffentliche Konstruktor verfügt nur über die beiden Parameter für Minimum und Maximum. Er bezieht sich auf den internen Konstruktor und übergibt diesem, neben den beiden Grenzwerten, auch einen mit new Random() erzeugten Ran-dom-Generator. Das Highlander-Prinzip sollte beachtet werden, damit es in den Konstruktoren nicht zur Verletzung des Prinzips Don't Repeat Yourself (DRY) kommt. Der öffentliche Konstruktor könnte ja die Grenzwerte selbst an die Felder zuweisen, dann würden diese Zuweisungen jedoch an zwei Stellen auftreten.

Eine weitere interessante Implementierung bietet der RollingIntGenerator. Er liefert, ausgehend von einem Minimumwert, immer den nächstenWert, bis er beim Maximumwert angekommen ist. Dann wird wieder von vorn begonnen. Bei diesem Generator lag die Herausforderung darin, korrekt mit dem größtmöglichen int-Wert (int.MaxValue) umzugehen. Ohne UnitTests wäre das ein elendiges Rumprobieren geworden. So war es ganz leicht.


[Abb. 8] Das fertige Portal.

Für Stringwerte habe ich einen Generator implementiert, der eine Liste von Strings erhält und daraus zufällig einen auswählt. Die zur Verfügung stehenden Strings habe ich im Konstruktor als Para-meter-Array definiert, siehe Listing 9.

Listing 9: Zufällig einen Stringwert auswählen.

internal RandomSelectedStringsGenerator(Random random, params string[] values) {

this.random = random;

this.values = values;

}

Das ist für die Unit-Tests ganz angenehm, weil man einfach eine beliebige Liste von Stringwerten übergeben kann:

sut = new RandomSelectedStringsGenerator(

new Random(0), "Apfel", "Birne",

"Pflaume");

Bei der Verwendung des Generators aus Sicht einer Benutzerschnittstelle ist es wünschenswert, einen String zu übergeben, der eine Liste von Werten enthält, die mit Semikolon getrennt sind:

"Apfel; Birne; Pflaume"

Um das zu ermöglichen, habe ich eine separate Extension Method ToValues() implementiert, die einen String entsprechend zerlegt. Diese Methode kann bei Bedarf in den Konstruktoraufruf eingesetzt werden:

"Apfel; Birne; Pflaume".ToValues().ToArray()

Natürlich hätte ich das Zerlegen des Strings in die Einzelwerte auch im entsprechenden Generator implementieren können. Dann hätte der sich aber um mehr als eineVerantwortlichkeit gekümmert. Ferner war die Implementierung so etwas einfacher, da ich mich jeweils auf eine einzelne Aufgabenstellung konzentrieren konnte.

Dojos für Entwickler

Подняться наверх