Stefan Lieser

Dojos für Entwickler 2


Скачать книгу

Windows 7. Nachdem ich diese in das Projekt kopiert hatte, habe ich wieder mit dem Windows Explorer Stichwörter vergeben, siehe Abbildung 4.

figure2l4

      [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 meta­data.Key­words 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 Pla­tine entsprechend aufruft. Weiter geht es ­also in Listing 10 mit der Platine.

      Listing 10

      Den Flow zusammensetzen.

      public class Eindeutige_Stichwörter_ermitteln { private Action<Tuple<string, string>> process; public Eindeutige_Stichwörter_ermitteln() { var dateinamen_suchen = new Dateinamen_suchen(); var alle_Stichwörter_ermitteln = new Alle_Stichwörter_ermitteln(); var eindeutige_Stichwörter_filtern = new Eindeutige_Stichwörter_filtern(); dateinamen_suchen.Result += alle_Stichwörter_ermitteln.Process; alle_Stichwörter_ermitteln.Result += eindeutige_Stichwörter_filtern.Process; eindeutige_Stichwörter_filtern.Result += x => Result(x); process = path_und_SearchPattern => dateinamen_suchen.Process( path_und_SearchPattern); } public void Process(Tuple<string, string> path_und_SearchPattern) { process(path_und_SearchPattern); } public event Action<IEnumerable< string>> Result; }

      Ich habe hier bewusst keine Dependency Injection betrieben, sondern in der Platine werden die benötigten Bauteile direkt mit new instanziert. Die Abhängigkeiten über den Konstruktor zu injizieren hätte mir den Vorteil gebracht, dass ich dann im Test gegen Attrappen hätte testen können. Doch das ist mir für Platinen viel zu mühsam. Denn es liefe dann auf Tests hinaus, in denen ein Mock-Framework zum Einsatz kommen muss, um damit die Attrappen zu erzeugen. Weil die Platine selbst keine Logik enthält, sondern nur für die Verdrahtung der Bauteile zuständig ist, begnüge ich mich mit einem ohnehin notwendigen Integrationstest. Der Erkenntnisgewinn wäre durch einen Unit-Test der Platine, der dann auf Attrappen setzen müsste, nicht größer.

      Das Verdrahten der Bauteile geschieht im Konstruktor der Platine. Nachdem die benötigten Bauteile instanziert sind, werden sie in der richtigen Reihenfolge verdrahtet, indem jeweils eine Process-Methode an einen Result-Event gebunden wird. Am Ende der Kette muss der Result-Event des letzten Bauteils den Result-Event der Platine auslösen. Da zum Zeitpunkt der Verdrahtung innerhalb des Konstruktors noch niemand an den Result-Event der Platine gebunden sein kann, erfolgt die Weiterleitung an diesen Event mittels Lambda-Expression. Um beim Aufruf der Process-Methode der Platine die Process-Methode des ersten Bauteils aufrufen zu können, wird der korrekte Aufruf in eine Action als Feld der Klasse abgelegt.

      Der Integrationstest zur Platine arbeitet auf denselben Testdaten wie die Tests der Bauteile. Dadurch war der Integrationstest in Listing 11 schnell fertiggestellt. Der Test ermittelt die Stichwörter aller JPEG-Dateien im Verzeichnis testdaten. Anschließend wird über Assert überprüft, ob die korrekten Stichwörter vorliegen.

      Listing 11

      Integrationstest.

      [TestFixture] public class Eindeutige _Stichwörter_ermitteln_Tests { private Eindeutige _Stichwörter_ermitteln sut; private IEnumerable<string> result; [SetUp] public void Setup() { sut = new Eindeutige _Stichwörter_ermitteln(); sut.Result += x => result = x; } [Test] public void Integrationstest() { sut.Process(new Tuple<string, string>("testdaten", "*.jpg")); Assert.That(result.ToArray(), Is.EqualTo(new[] { "A", "B", "C", "D", "E" })); } }

      Eine App

      Als letzter Schritt musste nun die Platine noch in eine Konsolenanwendung integriert werden, siehe Listing 12.

      Listing 12

      Integration in eine Konsolenanwendung.

      public static class Program { public static void Main(string[] args) { var eindeutige_Stichwörter_Ermitteln = new Eindeutige_Stichwörter_ermitteln(); eindeutige_Stichwörter_Ermitteln.Result += stichwörter => { foreach (var stichwort in stichwörter) { Console.WriteLine(stichwort); } }; eindeutige_Stichwörter_Ermitteln.Process( new Tuple<string, string>( args[0], args[1])); } }

      Ich habe mich entschieden, den Pfad und die Suchmaske aus den Parametern der Kommandozeile zu entnehmen. Hier erfolgt keine Prüfung, ob