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

wichtig es ist, sich vor der Implementierung ein paar Gedanken zur Architektur zu machen. Der erste Gedanke, das Erzeugen der Daten vom Speichern zu trennen, liegt auf der Hand und wurde in der Aufgabenstellung schon erwähnt. Doch wie geht man generell vor, wenn für eine Aufgabenstellung eine Architektur entworfen werden soll? Ganz einfach: Man malt den „kleinen König". Den gibt es immer, denn er ist schließlich derjenige, der die Anforderungen formuliert hat. Er ist der Grund dafür, dass das System überhaupt gebaut wird. Das zu implementierende System als Ganzes kann man auch sofort hinmalen. Damit liegt man nie verkehrt. Es ergibt sich damit das in Abbildung 1 gezeigte Bild.

      [Abb. 1] SystemUmweltDiagramm, Version 1.

      Das Diagramm nennt sich System-Um-welt-Diagramm, da es das System in seiner Umwelt zeigt. In der Umwelt des Systems gibt es immer mindestens einen Client, den kleinen König, der das System bedient. Bei manchen Systemen mag es mehrere unterschiedliche Clients geben, das spielt für den Testdatengenerator jedoch keine Rolle. Die zweite Kategorie von Elementen in der Umwelt stellen Ressourcen dar. Diese liegen außerhalb des zu erstellenden Systems und sollten daher in das System-Umwelt-Dia-gramm aufgenommen werden, denn unser System ist von diesen Ressourcen abhängig. Im Fall des Testdatengenerators sind als Ressourcen in der Umwelt CSV-Dateien und Datenbanken denkbar. Irgendwo müssen die generierten Testdaten schließlich hin. Folglich ergänze ich das System-Um-welt-Diagramm um diese Ressourcen. Das Ergebnis ist in Abbildung 2 zu sehen.

      Komponente

       Eine Komponente ist eine binäre Funktionseinheit mit separatem Kontrakt:

       Binär bedeutet hier, dass die Komponente an den Verwendungsstellen binär referenziert wird. Es wird also bei der Verwendung keine Referenz auf das entsprechende Visual-Studio-Projekt gesetzt, sondern eine Referenz auf die erzeugte Assembly. Separater Kontrakt bedeutet, dass das Interface für die Komponente in einer eigenen Assembly abgelegt ist und nicht in der Assembly liegt, in welcher die Komponente implementiert ist. Daraus folgt, dass eine Komponente immer aus mindestens zwei Assemblies besteht, nämlich einer für den Kontrakt und einer für die Implementierung. Und natürlich gehören Tests dazu - also besteht jede Komponente aus mindestens drei Projekten.

      [Abb. 2] System-Umwelt-Diagramm, Version 2.

      Wer nun glaubt, ein solches Diagramm sei ein Taschenspielertrick, um Zeit zu schinden, ohne Nutzen für den Architekturentwurf, der irrt. Denn aus diesem Bild wird bereits deutlich, welche Komponenten mindestens entstehen müssen. Den Begriff Komponente verwende ich hier mit einer festen Bedeutung, siehe dazu die Erläuterungen im Kasten.

      Der Kern des Systems sollte gegenüber der Umwelt abgeschirmt werden, weil das System die Umwelt nicht kontrollieren kann. Die Umwelt kann sich verändern. Es können etwa neue Clients hinzukommen oder auch zusätzliche Ressourcen. Folglich müssen auf der Umrandung des Systems Komponenten entstehen, die den Kern des Systems über definierte Schnittstellen gegenüber der Umwelt isolieren. Andernfalls würde der Kern des Systems immer wieder von Änderungen in der Umwelt betroffen sein und wäre damit sehr anfällig. Und darin liegt die Bedeutung des System-Umwelt-Diagramms: Es zeigt, welche Komponenten das System von der Umwelt abschirmen.

      Für Clients, die das System verwenden, bezeichnen wir die Komponente, über welche der Client mit dem System interagiert, als Portal. In Abhängigkeitsdiagrammen werden Portale immer als Quadrate dargestellt. Die Interaktion des Systems mit Ressourcen erfolgt über Adapter. Diese werden durch Dreiecke symbolisiert. Im konkreten Fall des Testdatengenerators können wir aufgrund des System-Umwelt-Diagramms also schon vier Komponenten identifizieren, siehe Abbildung 3:

       Portal,

       CSV-Adapter,

       Datenbank-Adapter,

       Testdatengenerator.

      [Abb. 3] System-Umwelt-Komponenten.

      Die Komponenten sollten normalerweise allerdings nicht im System-Umwelt-Diagramm eingezeichnet werden, weil dort sonst zwei Belange vermischt werden. Es soll hier nur gezeigt werden, dass sich Portal und Adapter immer sofort aus dem Sys-tem-Umwelt-Diagramm ergeben. Aus dem in Abbildung 3 gezeigten Diagramm lässt sich das in Abbildung 4 gezeigte Abhängigkeitsdiagramm ableiten.

      [Abb. 4] Abhängigkeitsdiagramm.

      Den Kern zerlegen

      Nachdem ich diese Komponenten identifiziert hatte, habe ich die Aufgabenstellung Erzeugen der Daten zerlegt. Aufgabe des Testdatengenerators ist es, Datenzeilen zu erzeugen. Dabei soll jede Datenzeile aus mehreren Spalten bestehen. Diese Aufgabe kann in folgende Funktionseinheiten zerlegt werden:

       Erzeugen eines einzelnen Wertes,

       Erzeugen einer Zeile,

       Erzeugen mehrerer Zeilen.

      Dabei scheint die Trennung in das Erzeugen einer Zeile und das Erzeugen mehrerer Zeilen auf den ersten Blick möglicherweise etwas merkwürdig. Wenn eine Zeile erzeugt werden kann, genügt doch eine simple Schleife, und schon können mehrere Zeilen erzeugt werden. Dennoch halte ich es für wichtig, diese beiden Funktionseinheiten zu identifizieren. Denn für die testgetriebene Entwicklung ist es nützlich, im Vorfeld zu wissen, welche Funktionseinheiten auf einen zukommen. So fällt es nämlich viel leichter, ausreichende Testfälle zu finden, sprich: die Anforderungen zu klären. Und bei den Anforderungen liegt die Herausforderung eher darin, klar zu definieren, was die Anforderungen an das Erzeugen einer einzelnen Zeile sind. Dies dann zu übertragen auf die Erzeugung mehrerer Zeilen ist in der Tat trivial. Aber ohne die Trennung würde möglicherweise nur eine Funktionseinheit entstehen, die mehrere Datenzeilen erzeugt. Das würde die testgetriebene Entwicklung unnötig erschweren.

      Nachdem ich für das Erzeugen der Daten die Funktionseinheiten identifiziert hatte, habe ich überlegt, welche davon Komponenten werden sollen. Erst Komponenten erlauben eine parallele Entwicklung von Funktionseinheiten durch mehrere Entwickler oderTeams gleichzeitig. Dies ist zwar hier nicht das Ziel, doch resultiert aus der Trennung von Kontrakt und Implementierung, dass die Komponenten austauschbar sind. Dies betrachte ich beim Testdatengenerator an einer Stelle für besonders wichtig: bei den Generatoren. Die werden später sicher immer wieder ergänzt werden. Da ist es hilfreich, wenn dann nicht jeweils die gesamte Anwendung neu übersetzt werden muss, sondern neue Generatoren mit geringem Aufwand ergänzt werden können. In einer weiteren Ausbaustufe wäre es sogar denkbar, die Generatoren zur Laufzeit zu laden. Dann könnten später beliebige zusätzliche Generatoren verwendet werden, ohne dass am Testdatengenerator selbst etwas geändert werden muss.

      Damit sind die Generatoren zunächst einmal eine Komponente. Eine andere Aufteilung wäre ebenfalls denkbar, man könnte Generatoren zum Beispiel nachTyp in Komponenten zusammenfassen. Eine Komponente mit Stringgeneratoren, eine für int-Generatoren et cetera. Zurzeit sind es nur wenige Generatoren, daher habe ich mich dafür entschieden, sie alle in einer Komponente unterzubringen. Innerhalb der Komponente habe ich die Generatoren nach Typ in Unterverzeichnisse geordnet. Dies ist in Abbildung 5 zu sehen.

      [Abb. 5] Generatoren, nach Typ geordnet, in Unterverzeichnissen.

      Eine weitere Komponente bildet die Funktionseinheit, die dafür zuständig ist, Zeilen aus Einzelwerten zu bilden. Diese Komponente habe ich DataPump genannt.

      Eine dritte Komponente bildet das Speichern der Daten. Implementiert habe ich einen CsvDataAdapter. Ein DbDataAdapter zum Speichern der Testdaten

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