Dojos für Entwickler 2. Stefan Lieser
Чтение книги онлайн.
Читать онлайн книгу Dojos für Entwickler 2 - Stefan Lieser страница 7
Als Nächstes kam das Bauteil Alle_Stichwörter_ermitteln an die Reihe. Auch hier habe ich erst implementiert und dann getestet. Zur Enttäuschung der Test-first-Verfechter muss ich auch hier mitteilen, dass das nachträgliche Schreiben der automatisierten Tests keine Probleme bereitet hat. Das verwundert mich natürlich nicht wirklich, denn vor der Implementation stand ja der Entwurf mit Flow-Design. Ich möchte hier nicht den Eindruck erwecken, dass Test-first eine überflüssige Praktik sei. Allerdings bringt es eben auch keinen Vorteil, sich sklavisch an diese Praktik zu halten. In vielen Fällen ist eine Test-first-Vorgehensweise sinnvoll, nämlich immer dann, wenn Unsicherheiten bei der Implementation vorhanden sind. Durch den Entwurf und den Spike war mir allerdings zu Beginn der Implementation klar, was auf mich zukommt. Während der Übungszeit Praktiken bewusst einmal anzuwenden und ein anderes Mal nicht, halte ich für eine nützliche Form der Reflexion.
Beim Ermitteln der Stichwörter habe ich auf die Erkenntnisse aus dem Spike zurückgegriffen. Die Aufgabe bestand darin, zu einer Aufzählung von Dateinamen alle verwendeten Stichwörter zu liefern. Durch die Verwendung von yield return sieht die Implementation recht übersichtlich aus, wie Listing 5 zeigt.
Listing 5
Stichwörter ermitteln.
public class Alle_Stichwörter_ermitteln { public void Process(IEnumerable< string> dateinamen) { Result(Stichwörter_ermitteln(dateinamen)); } private IEnumerable<string> Stichwörter_ermitteln( IEnumerable<string> dateinamen) { foreach (var dateiname in dateinamen) { var stichwörter = Stichwörter _ermitteln(dateiname); foreach (var stichwort in stichwörter) { yield return stichwort; } } } private IEnumerable<string< Stichwörter_ermitteln(string dateiname) { BitmapSource img = BitmapFrame.Create( new Uri(dateiname, UriKind.Relative)); var metadata = (BitmapMetadata)img.Metadata; if (metadata.Keywords == null) { yield break; } foreach (var keyword in metadata.Keywords) { yield return keyword; } } public event Action<IEnumerable< string>> Result; }
Beim Aufruf der Process-Methode wird die gesamte Aufzählung der Dateinamen an die Methode Stichwörter_ermitteln übergeben. Diese hat einen Return-Wert vom Typ IEnumerable<string>, sodass yield return verwendet werden kann. Das war mein Ziel. Doch die Methode erledigt nicht die gesamte Arbeit, sondern lässt eine Überladung von Stichwörter_ermitteln die Arbeit für eine einzelne Datei ausführen. Dafür habe ich mich entschieden, damit die Methoden übersichtlich und klein bleiben. Die erste Überladung iteriert über alle Dateinamen und über die gelieferten Stichwörter in Form von zwei geschachtelten foreach-Schleifen. Listing 6 zeigt, dass das mithilfe von LINQ deutlich knackiger geht. Ich höre allerdings schon die Kritiker rufen: „Das versteht doch keiner mehr.“ Mag sein. Wer sich bislang nicht intensiv mit LINQ auseinandergesetzt hat, wird mit dieser knappen Formulierung möglicherweise Probleme haben. Aber wir programmieren ja auch nicht mehr in Assembler, nur weil irgendjemand C-Code nicht lesen kann.
Listing 6
LINQ nutzen.
private IEnumerable<string> Stichwörter_ermitteln( IEnumerable<string> dateinamen) { return dateinamen.SelectMany( Stichwörter_ermitteln); }
Um nun dieses Bauteil automatisiert testen zu können, bedarf es wieder Testdaten in Form von JPEG-Dateien. Auch diese Dateien habe ich im Projekt abgelegt, wie Abbildung 3 zeigt. Es handelt sich um vier JPEG-Dateien aus dem Lieferumfang von Windows 7. Nachdem ich diese in das Projekt kopiert hatte, habe ich wieder mit dem Windows Explorer Stichwörter vergeben, siehe Abbildung 4.
[Abb. 4]
Mehrere Stichwörter vergeben.
Auch diese Testdateien werden in das Ausgabeverzeichnis des Testprojektes kopiert, sodass sie im Test über einen relativen Dateinamen angesprochen werden können, siehe Listing 7.
Listing 7
Test: Stichwörter ermitteln.
[TestFixture] public class Alle_Stichwörter_ermitteln_Tests { private Alle_Stichwörter_ermitteln sut; private IEnumerable<string> result; [SetUp] public void Setup() { sut = new Alle_Stichwörter_ermitteln(); sut.Result += x => result = x; } [Test] public void Ein_einzelner_Dateiname() { sut.Process(new[] { @"testdaten\Lighthouse.jpg" }); Assert.That(result.ToArray(), Is.EqualTo(new[] { "A", "B", "C" })); } [Test] public void Mehrere_Dateinamen() { sut.Process(new[] { @"testdaten\Lighthouse.jpg", @"testdaten\Penguins.jpg", @"testdaten\Tulips.jpg" }); Assert.That(result.ToArray(), Is.EqualTo(new[] { "A", "B", "C", "C", "D", "E" })); } [Test] public void Eine_Datei_ohne_Stichwörter() { sut.Process(new[] { @"testdaten\Desert.jpg" }); Assert.That(result.ToArray(), Is.EqualTo(new string[] {})); } }
Der erste Test stellt sicher, dass die Stichwörter einer einzelnen Datei gelesen werden können. Durch den zweiten Test wird überprüft, ob dies auch für mehrere Dateien möglich ist. Dabei sieht man, dass doppelt verwendete Stichwörter hier noch nicht herausgefiltert werden.
Der letzte Test ist erst hinzugekommen, nachdem ich die ganze Anwendung fertig hatte und das Programm zum ersten Mal auf einem „echten“ Bildverzeichnis lief. Dabei zeigte sich, dass sich das Programm bei Dateien, die kein Stichwort enthalten, auf die Nase legte. Das liegt daran, dass metadata.Keywords den Wert null liefert anstelle einer leeren Aufzählung. Nachdem ich das Problem durch den dritten Test in Listing 7 reproduziert hatte, konnte ich die Implementation entsprechend korrigieren. Dort wird auf null geprüft und in diesem Fall durch yield break eine leere Aufzählung geliefert.
Dieses Problem wäre übrigens auch nicht bei einer Test-first-Vorgehensweise zum Vorschein gekommen. Nach der Implementation durch den Entwickler sind trotz automatisierter Tests immer noch explorative Tests erforderlich. Das hier konkret vorliegende Problem hätte wohl auch im realen Leben ein Entwickler gefunden. In größeren Zusammenhängen wäre es möglicherweise aber auch erst bei der Qualitätskontrolle durch das Testteam aufgefallen.
Das dritte und letzte Bauteil war dank LINQ rasch realisiert: Eine Aufzählung musste so gefiltert werden, dass keine doppelten Einträge mehr auftauchen. Darum kümmert sich Distinct, siehe Listing 8. Die Tests dazu sind trivial, wie Listing 9 zeigt.
Listing 8
Doppelte Einträge herausfiltern.
public class Eindeutige_Stichwörter_filtern { public void Process(IEnumerable<string> stichwörter) { Result(stichwörter.Distinct()); } public event Action<IEnumerable<string>> Result; }
Listing 9
Test auf eindeutige Stichwörter.
[TestFixture] public class Eindeutige_Stichwörter_filtern_Tests { private Eindeutige_Stichwörter_filtern sut; private IEnumerable<string> result; [SetUp] public void Setup() { sut = new Eindeutige_Stichwörter_filtern(); sut.Result += x => result = x; } [Test] public void Nicht_eindeutige_Stichwörter() { sut.Process( new[]{"A", "B", "A", "C", "C", "B"}); Assert.That(result.ToArray(), Is.EqualTo(new[]{"A", "B", "C"})); } [Test] public void Ohnehin_schon _eindeutige_Stichwörter() { sut.Process(new[]{"A", "B"}); Assert.That(result.ToArray(), Is.EqualTo(new[]{"A", "B"})); } }
Nun fehlen nur noch eine Platine, die den Flow zusammensetzt, sowie ein Kommandozeilenprogramm, welches die