In diesem Kapitel betrachten wir die Montage des Systems, die Integration. Die Integration ist ein wichtiger und schwieriger Schritt der Software-Entwicklung; er wird oft unterschätzt. Nachfolgend diskutieren wir den Zusammenhang, der zwischen Entwurf, Test und Integration besteht. Dann stellen wir einige Strategien für die Integration vor und erörtern ihre Vor- und Nachteile. Am Ende steht eine kurze Liste mit Empfehlungen zur Integration.
integration — The process of combining software components, hardware components, or both into an overall system.
IEEE Std 610.12 (1990)
Wenn die Bestandteile des Systems realisiert, angepasst oder beschafft sind, können sie zusammengefügt werden. Im Zuge der Integration soll aus ihnen ein vollständiges und funktionsfähiges System entstehen. Den Bau- und Montageplan finden wir in der Software-Architektur, denn diese legt die Komponenten, die Schnittstellen zwischen den Komponenten und die Schnittstellen nach außen fest. Das der Architektur zu Grunde gelegte Metamodell gibt die Ebenen der Integration vor, die typischerweise hierarchisch angeordnet sind. Basiert eine Architektur zum Beispiel auf Subsystemen, die aus Komponenten bestehen, die wiederum Module enthalten, dann sind dies auch die Ebenen der Integration. Solange das System noch nicht vollständig integriert ist, wird das Ergebnis jedes Integrationsschritts als teilintegriertes System bezeichnet.
Da die Komponenten eines Systems meist von mehreren Entwicklern erstellt werden, die wenig Grund und Gelegenheit haben, sich über ihre Arbeit auszutauschen, muss damit gerechnet werden, dass ein teilintegriertes System Fehler an den Schnittstellen enthält: Daten werden unterschiedlich interpretiert, Operationen sind nicht so realisiert, wie es ihre Benutzer erwartet hatten, die Behandlung von Sonderfällen wurde jeweils auf der anderen Seite vermutet usw. Darum muss das teilintegrierte System vor jeder Verwendung und weiteren Integrationen getestet und korrigiert werden. Dieser Test wird als Integrationstest bezeichnet.
integration testing — Testing in which software components, hardware components, or both are combined and tested to evaluate the interaction between them.
IEEE Std 610.12 (1990)
Während der Modultest die Fehler in einer einzelnen Komponente aufspüren soll, zielt der Integrationstest darauf ab, Konsistenzprobleme zwischen den Komponenten aufzudecken.
Die Integration und der Integrationstest dürfen nicht in der Entwicklungsumgebung stattfinden. Dazu sollte eine eigene Umgebung, die Integrationsumgebung (siehe Abschnitt 21.4), eingerichtet werden.
Ein teilintegriertes System ist im Allgemeinen nicht ausführbar; für den Integrationstest werden darum Testtreiber und Platzhalter (Stubs) benötigt. Ein Testtreiber versorgt das teilintegrierte System mit Testeingaben, ein Platzhalter vertritt eine Komponente, die vom teilintegrierten System benötigt wird, aber noch nicht integriert ist. Ein Platzhalter liefert entweder konstante Werte oder simuliert das Verhalten der ersetzten Komponente mehr oder minder realistisch.
Abbildung 20–1 zeigt ein Integrationsszenario, bei dem sowohl ein Testtreiber als auch Platzhalter benötigt werden. Die Komponente »Report-Generator« wird in das bestehende teilintegrierte System integriert.
Abb. 20–1 Einsatz von Testtreiber und Platzhaltern bei der Integration
Damit das Resultat getestet werden kann, müssen für die vom Report-Generator benutzten Komponenten »XML-Formatierer« und »Excel-Formatierer« Platzhalter bereitgestellt werden. Zusätzlich ist ein Testtreiber zu implementieren, um die Schnittstelle des Report-Generators zu bedienen. Je nach gewählter Integrationsstrategie werden mehr oder weniger Testtreiber und Platzhalter benötigt. Die Entwicklung »intelligenter« Platzhalter, die das Verhalten der fehlenden Komponente simulieren, ist aufwändiger als die Entwicklung von Testtreibern. Dem sollte die Integrationsstrategie Rechnung tragen.
Grundsätzlich ist es möglich, alle (oder viele) Komponenten in einem einzigen Schritt zu integrieren; dieses Vorgehen wird auch als Big-Bang-Integration bezeichnet. Testtreiber und Platzhalter sind nicht notwendig, denn nach der Integration ist das System vollständig und kann ohne Hilfsmittel getestet werden.
Trotzdem kommt die Integration in einem Schritt in der Praxis kaum in Frage. Wenn jede Komponente mit einer gewissen Wahrscheinlichkeit Fehler enthält und durch Inkonsistenzen weitere Fehler hinzukommen, müssen die Tester mit einem System arbeiten, das kaum ausführbar ist; die Suche nach den Fehlern ist schwierig, da sie in einem großen, den Beteiligten kaum bekannten System verteilt sind. Selbst wenn die Entwickler noch am Test teilnehmen, kennen sie ja jeweils nur ihre Komponenten.
Daher kommt diese Art der Integration nur in Frage, wenn (wie beim Cleanroom-Ansatz, siehe Abschnitt 10.5) schon vor der Integration eine hohe Qualität der Komponenten und gute Konsistenz der Schnittstellen gewährleistet sind. Andernfalls ist die inkrementelle Integration vorzuziehen.
Bei der inkrementellen Integration werden die Komponenten einer Integrationsebene einzeln oder in kleinen Gruppen integriert. Je nachdem, ob man mit dem Hauptprogramm (z. B. der Komponente, die die Bedienoberfläche realisiert) oder mit den Basiskomponenten beginnt, spricht man von Top-down- oder Bottom-up-Integration. Wenn alle Komponenten rechtzeitig verfügbar sind, bestimmen die Benutzt-Beziehungen zwischen den Komponenten die Reihenfolge der Integration. Andernfalls versucht man, alle fertigen Komponenten so rasch wie möglich zu integrieren.
Abbildung 20–2 zeigt schematisch die Aktivitäten bei der inkrementellen Integration. Nachdem die zur Integration vorgesehenen Komponenten zur Verfügung stehen, werden die Integrationstestfälle und die benötigten Testtreiber und Platzhalter entwickelt. Danach wird zuerst die syntaktische Kompatibilität der Komponenten geprüft; anschließend wird der eigentliche Integrationstest durchgeführt. Werden bei diesen Prüfungen Fehler entdeckt, dann werden die fehlerhaften Software-Einheiten (Komponenten, Testtreiber, Platzhalter, Testdaten) von den Entwicklern korrigiert und die Integration und die Prüfungen wiederholt.
Abb. 20–2 Aktivitäten der inkrementellen Integration
Die Top-down-Integration folgt der hierarchischen Struktur der Architektur. Definiert die Architektur beispielsweise eine Schichtenstruktur, so werden zuerst das Hauptprogramm und die Komponenten der obersten Schicht integriert, dann die Komponenten der nächsten Schicht darunter und so fort.
Diese Strategie hat den Vorteil, dass bereits sehr früh ein ausführbares System entsteht. Ihr Nachteil ist, dass unter Umständen sehr viele Platzhalter erstellt werden müssen. Um das System wirklich ausführbar zu machen, müssen diese Platzhalter eine gewisse Funktionalität bieten; ihre Erstellung ist entsprechend aufwändig.
Bei der Bottom-up-Integration werden zuerst die Komponenten integriert, die keine Dienstleistungen anderer Komponenten benötigen. Dann werden die Komponenten hinzugefügt, die diese Basiskomponenten benutzen. Im letzten Schritt wird das Hauptprogramm aufgesetzt. Wenn möglich, folgt man der BenutztBeziehung streng rückwärts; eine Komponente wird erst integriert, wenn sie bereits alle benötigten Komponenten im teilintegrierten System vorfindet. Nur bei zyklischen Abhängigkeiten braucht man Platzhalter, oder man muss versuchen, alle zyklisch abhängigen Komponenten zugleich zu integrieren.
Vorteilhaft bei dieser Integrationsstrategie ist, dass keine oder nur wenige Platzhalter benötigt werden. Der Aufwand für den Integrationstest ist dadurch relativ gering, man braucht nur für jeden Schritt einen neuen Testtreiber. Die Prüfung des Gesamtsystems ist erst nach dem letzten Integrationsschritt möglich.
Einen ganz anderen Ansatz zur Integration finden wir beim Extreme Programming (siehe Abschnitt 10.6) und anderen agilen Ansätzen.
Damit rasch ein lauffähiges System entsteht und die Entwickler, die in Paaren arbeiten, gleichberechtigt an verschiedenen Teilen des Systems arbeiten können, muss es möglich sein, neue und geänderte Komponenten einfach und schnell zu integrieren. Dadurch wird die Integration zu einem fortlaufenden Prozess (Lippert, Roock, Wolf, 2002). Sobald eine neue Komponente fertig oder eine bereits vorhandene modifiziert ist, wird sie integriert und getestet. Die Integration findet in einer separaten Umgebung – auf dem sogenannten Integrationsrechner – statt, der von den Entwicklungsumgebungen getrennt ist.
Es wird angestrebt, dass jedes Entwicklerpaar wenigstens einmal pro Tag seine Arbeitsergebnisse integriert und testet. Nur wenn der Test keine Fehler zeigt, bleibt der neue Code auf dem Integrationsrechner; sonst muss er wieder entfernt und der frühere Zustand des Systems wiederhergestellt werden.
Diese Art der Integration funktioniert nur, wenn das gesamte Entwicklungsteam auch physisch eng zusammenarbeitet. Da oft integriert wird, müssen die einzelnen Erweiterungen und Änderungen klein sein. Andernfalls dauert der einzelne Integrationsschritt zu lange, und die Integration wird zum Engpass.
In der Praxis treten diese Integrationsstrategien selten in Reinform auf, sondern es wird gleichzeitig top-down und bottom-up integriert. Abbildung 20–3 zeigt eine Architektur, die zehn Komponenten enthält.
Abb. 20–3 Integrationsrichtungen
In der Tabelle 20–1 sind mögliche Integrationssequenzen für die Top-down- und für die Bottom-up-Integration angegeben, wobei in jedem Schritt immer genau eine Komponente integriert wird (fett dargestellt). Im Schritt 0 findet eigentlich keine Integration statt. Er zeigt den Test der ersten Komponente (A bzw. J), da dazu auch Platzhalter und ein Testtreiber entwickelt werden müssen. Die kleinen Buchstaben repräsentieren Platzhalter, für die eingerahmten Komponenten werden Testtreiber benötigt. Wie man sieht, sind bei der Top-down-Integration viele Platzhalter, aber nur ein Testtreiber erforderlich. Die Bottom-up-Integration benötigt nur einen Platzhalter, da zwischen E und F eine zyklische Abhängigkeit besteht. Dafür erfordert sie viele Testtreiber, in jedem Schritt einen anderen.
Tab. 20–1 Schritte einer Top-down- und einer Bottom-up-Integration
Die links gewählte Reihenfolge minimiert nicht die Verwendung der Platzhalter. Wenn man die Reihenfolge ändert (A-B-D-G-H-J-C-F-E-I), benötigt man die neun verschiedenen Platzhalter nicht 25-mal, sondern nur 17-mal. Da der Integrationstest intensiver durchgeführt werden kann, wenn die Komponenten selbst und nicht ihre Platzhalter verwendet werden, ist die zweite Reihenfolge vorzuziehen. Bei der Bottom-up-Integration gibt es keine entsprechenden Variationen.
Eine detaillierte Betrachtung der Integrationsstrategien ist in Pagel und Six (1994) zu finden.
In einer idealen Welt ist die Integration kein Problem: Die Entwickler realisieren unabhängig voneinander die einzelnen Komponenten. Weil sie sich präzise an die Spezifikation halten und auf syntaktischer Ebene durch die strenge Typprüfung ohnehin alles stimmt, passen die Teile am Ende so zusammen, wie es geplant war. Der Integrationsaufwand ist gering und fällt nicht ins Gewicht.
In der Praxis sieht das ganz anders aus. Vor allem zwei Gründe stehen der mühelosen Integration entgegen:
Die Spezifikation ist schwammig und unvollständig. Sie wird darum von den Entwicklern frei und natürlich unterschiedlich interpretiert, allzu oft auch falsch verstanden. Das Ergebnis sind Komponenten, die nicht zusammenpassen.
Im System sollen Fremdkomponenten verwendet werden; das können vorhandene Subsysteme sein (die noch mit beträchtlichem Aufwand angepasst werden müssen), aber auch Komponenten, die gekauft oder von anderen Firmen entwickelt wurden. Oft ist in solchen Fällen die Information über die Fremdkomponenten völlig unzureichend; zudem stehen sie meist nicht rechtzeitig zur Verfügung.
Erst wenn die Integration beginnen soll, werden die Inkonsistenzen und Widersprüche sichtbar. Da es zu diesem Zeitpunkt – kurz vor dem Auslieferungstermin der Software – keine Chance mehr gibt, die Mängel gründlich zu analysieren und zu beheben oder gar eine schlechte Komponente zu ersetzen, müssen die Komponenten passend gemacht werden. Diese Arbeit ist aufwändig und findet nicht selten im panic mode statt. Wenn die Software endlich (verspätet) an den Kunden geht, zeigt sie schon die Spuren einer hastigen und oberflächlichen Wartung.
Mit modernen Sprachen und Methoden lassen sich diese Probleme weitgehend vermeiden. Strenge Typprüfung garantiert die syntaktische Kompatibilität. Die semantische Kompatibilität der Komponenten kann nur durch sorgfältige Planung und Spezifikation der Teile vor deren Codierung erreicht werden.
Die Integration eines großen, komplexen Systems ist aufwändig und mühsam und wird oft auch dadurch erschwert und verzögert, dass immer wieder Fragen aufkommen, die nur mit großer Mühe geklärt werden können.
Was bedeutet diese Warnung, können wir weitermachen oder nicht?
Ist es in Ordnung, dass im Test immer mehr Speicher belegt wird, oder handelt es sich um ein Speicherleck?
Warum wird laut Architekturplan diese Komponente integriert, obwohl ihre Funktionalität nie in Anspruch genommen wird?
Welche Komponente hätte die Datenstruktur initialisieren müssen? Oder ist das gar nicht erforderlich?
Da es sich vor allem um Schnittstellenprobleme handelt, ist nicht a priori klar, welche Entwickler zuständig sind. Wenn fremde Komponenten beteiligt sind, ist die Aufklärung von Inkonsistenzen und Inkompatibilitäten schwierig bis unmöglich.
Es ist darum zweckmäßig, auch die Integration zu planen und ihren Verlauf genau zu dokumentieren. Wenn das System einige Jahre oder gar Jahrzehnte lang eingesetzt wird, muss es im Zuge der Wartung immer wieder integriert und konfiguriert werden. Ein präziser Integrationsplan und das Logbuch der Integration erleichtern die erneute Durchführung und damit die Wartung.
Aus diesem Grund sehen einige Prozessmodelle die Integration und die damit verbundenen Dokumente explizit vor. So fordert beispielsweise das V-Modell XT (Abschnitt 10.3) im Vorgehensbaustein »SW-Entwicklung«, das Vorgehen bei der Integration und den Integrationsbauplan explizit zu beschreiben. Auch der Unified Process (Abschnitt 10.4) sieht Integrationspläne vor und verlangt, dass bei jeder Iteration eine Integration stattfinden muss. Ein Integrationsplan muss folgende Informationen enthalten:
Identifikation der zu integrierenden Komponenten
Technische Voraussetzungen der Integration, Ressourcen, Bedingungen und Einschränkungen
Organisation und Reihenfolge der Integration
Spezifische Angaben zu den einzelnen Integrationsschritten
Da die Integration typischerweise auf unterschiedlichen Ebenen stattfindet (Subsystem-, Komponenten-, Modulebene), muss der Integrationsplan alle diese Ebenen behandeln. Ob der Integrationsplan in weitere untergeordnete Integrationspläne aufgeteilt werden muss, hängt von den Projektstandards, vom Umfang und von der Komplexität der einzelnen Integrationsebenen ab.
Die folgenden Regeln sollen helfen, Fehler und Schwächen zu vermeiden, die bei der Integration oft vorkommen.
Die Integration planen!
Nur wenn die Integration explizit geplant wird, entsteht eine sinnvolle und umsetzbare Reihenfolge der Integrationsschritte. Dies ist eine Voraussetzung, um das System mit möglichst wenig Aufwand zusammenbauen zu können.
Frühzeitig mit der Integration beginnen!
Ein gutes Verfahren zur Integration besteht darin, damit zu beginnen, bevor codiert wird. Die im Entwurf definierten Komponenten werden als Platzhalter realisiert, d. h. durch Programmfragmente, die nur in Bezug auf die Schnittstellen den geplanten entsprechen. Sie werden dann im Laufe der Codierung zunächst mit den erforderlichen Kopfkommentaren, dann mit dem Code gefüllt. Auf diese Weise erhält man frühzeitig ein System, das sich jederzeit binden und sogar ausführen lässt.
Den Aufwand für die Integration und den Integrationstest nicht unterschätzen!
In vielen Fällen wird die Integration nicht oder nur oberflächlich geplant. Dies führt dazu, dass die dabei auftretenden Probleme massiv sind und das Projekt zeitlich und finanziell erheblich belasten. Aber auch wenn die Integration geplant wird, so sind die dafür vorgesehenen Zeiten und Ressourcen in vielen Fällen zu gering, da dieser Entwicklungsschritt notorisch unterschätzt wird.
Die Integrationsstrategie auf die Projektorganisation und auf den Entwicklungsprozess abstimmen!
Insbesondere bei großen Systemen, die verteilt entwickelt werden, muss die Architektur die Organisationsform des Entwicklungsprojekts berücksichtigen. Diese wirkt sich dann natürlich auch auf die Integrationsstrategie aus. Melvin Convey hat diesen Zusammenhang erkannt und folgendermaßen formuliert.
Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.
Melvin Conway (1968, S. 31)
Ebenso muss bei der Planung der Integration berücksichtigt werden, ob das System inkrementell oder im Ganzen entwickelt wird. Der gewählte Entwicklungsprozess und die Organisationsstruktur beeinflussen sowohl die Architektur als auch die Integration.
Den gesamten Aufwand für die Integration präzise erfassen!
Damit steigen die Chancen, dass dieser Schritt im nächsten Projekt besser geplant wird.
Integrationsrisiken erkennen und reduzieren!
Wenn es Zulieferungen gibt, die keiner Kontrolle durch das Projekt unterliegen, für die Durchführung der Integration aber unbedingt erforderlich sind, sollte ein »Plan B« in der Schublade liegen, der eine Katastrophe verhindert, wenn die Zulieferung sich als unbrauchbar erweist oder nicht rechtzeitig eintrifft. Möglicherweise kann vorläufig eine andere, viel einfachere Komponente integriert werden, oder es wird zunächst nur eine abgemagerte Variante des Systems fertiggestellt, die ohne die Zulieferung auskommt. Wenn das Problem bereits akut ist, müssen die entsprechenden Vorbereitungen schon getroffen sein.