pdf, ca. 250 kB - Persönliche Webseiten der Informatik

Werbung
Die Rekonstruktion der Dynamik objektorientierter Systeme
Stichworte
Rekonstruktion, Programmverfolgung, Laufzeitverhalten, dynamische Struktur,
Botschaftenfluss, dynamisches Testen
Zusammenfassung
In dieser Diplomarbeit wird ein Ansatz beschrieben, mit dem das Laufzeitverhalten einer objektorientierten Anwendung automatisch verfolgt wird. Durch Beobachtung und Aufzeichnung der Programmvorgänge werden die dynamische Struktur der Anwendung oder relevante Teile davon systematisch erfasst. Auf Basis der
gewonnenen Informationen wird ein Modell erstellt, das den Vergleich der Implementation mit ihrer Spezifikation ermöglicht. Durch diese Rekonstruktion wird
der dynamische Testprozess unterstützt. Die Machbarkeit des Ansatzes wird mit
einem Prototypen demonstriert, der im Rahmen dieser Arbeit implementiert wurde.
The reconstruction of the dynamic of object-orientied systems
Keywords
reconstruction, programtracing, runtime behaviour, dynamic structure,
message flow, dynamic testing
Abstract
This diploma thesis describes an approach in which the runtime behaviour of an
object-oriented system is traced automatically. The dynamic structure of the application or relevant parts of it are obtained systematically by observation and
recording the programs process. On the base of the obtained information a model
is created which enables the comparison of the implementation to its specification. This reconstruction supports the dynamic test procedure. The feasibility of the
approach is demonstrated by a prototype which was implemented as a part of this
diploma thesis.
Inhaltsverzeichnis
Abkürzungsverzeichnis
iv
Abbildungsverzeichnis
v
Tabellenverzeichnis
vi
1
Einleitung
1.1 Ziel der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Aufbau der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . .
1
3
3
2
Problembereich
2.1 Merkmale und Eigenschaften objektorientierter Systeme
2.1.1 Kapselung . . . . . . . . . . . . . . . . . . . .
2.1.2 Message Sending . . . . . . . . . . . . . . . . .
2.1.3 Beziehungen . . . . . . . . . . . . . . . . . . .
2.1.4 Polymorphie . . . . . . . . . . . . . . . . . . .
2.2 Situation des Entwicklers . . . . . . . . . . . . . . . . .
2.3 Zusammenfassung . . . . . . . . . . . . . . . . . . . .
5
5
6
6
6
7
7
9
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
Programmverfolgung
11
3.1 Debuggen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.2 Protokollieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4
Modellierung komplexer Systeme
4.1 Strukturen komplexer Systeme
4.2 Modellierung . . . . . . . . .
4.3 Modellaustauschformat XMI .
4.4 Zusammenfassung . . . . . .
ii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
16
17
17
18
19
5
Prototyp
5.1 Analyse . . . . . . . . .
5.1.1 Anwendungsfälle
5.1.2 Anforderungen .
5.2 Architektur . . . . . . .
5.2.1 Systementwurf .
5.2.2 Objektentwurf .
5.3 Implementierung . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
23
26
30
31
33
40
6
Fallbeispiel
42
6.1 Bewertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7
Schluss
46
7.1 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . 48
Literaturverzeichnis
49
Webseiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Glossar
54
A Fachliches Modell
56
B Fallbeispiel
B.1 Quellcode der Klasse PersonTest . . . . . . . .
B.2 Quellcode der Klasse Person . . . . . . . . . .
B.3 Systemtrace des Fallbeispiels . . . . . . . . . .
B.4 Metrik zur Messung der Programmaktivitäten .
B.5 XMI-Modell der Interaktionen des Fallbeispiels
57
57
58
59
63
65
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
C Installationshinweise
72
C.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
C.2 Benutzungshinweise . . . . . . . . . . . . . . . . . . . . . . . . 72
iii
Abkürzungsverzeichnis
CASE
Computer Aided Software Engineering
DTD
Document Type Definition
GUI
Graphical User Interface
JVM
Java Virtual Machine
JVMDI
Java Virtualmachine Debug Interface
JVMPI
Java Virtualmachine Profiling Interface
JPDA
Java Platform Debug Architecture
SDK
Software Development Kit
OMG
Object Management Group
UML
Unified Modelling Language
XMI
XML-Metadata Interchange
XML
Extended Markup Language
iv
Abbildungsverzeichnis
5.1
5.2
5.3
5.4
5.5
5.6
5.7
Anwendungsfalldiagramm . . . . . . . .
Szenario: Laufzeitverhalten aufzeichnen .
Szenario: Dynamischen Test durchführen
Systemarchitektur . . . . . . . . . . . . .
Paketstruktur . . . . . . . . . . . . . . .
Repository . . . . . . . . . . . . . . . . .
Tracer . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
22
24
25
31
32
35
37
6.1
6.2
Komponentenbrowser . . . . . . . . . . . . . . . . . . . . . . . .
Aktionsmonitor . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
43
A.1 Konzeptionelles Fachmodell . . . . . . . . . . . . . . . . . . . .
56
v
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Tabellenverzeichnis
5.1
Verfügbare Informationen der Methodenereignisse . . . . . . . .
vi
39
Kapitel 1
Einleitung
Interaktive Softwaresysteme zeichnen sich u.a. dadurch aus, dass sie mit externen
Akteuren kommunizieren. Als Interaktionspartner kommen grundsätzlich neben
Menschen auch andere Softwaresysteme oder Maschinen in Frage. In der vorliegenden Arbeit werden alle Interaktionspartner als Benutzer bezeichnet. Die Aktivitäten des Softwaresystems werden durch Benutzereingaben ausgelöst. Hierfür
stellt das System eine Benutzungsschnittstelle bereit. Die Benutzungsschnittstelle
nimmt die Eingabedaten entgegen und leitet sie zur Verarbeitung an die interne
Funktionskomponente weiter. Auch die Ausgabe bzw. Präsentation des Bearbeitungsergebnisses erfolgt durch die Benutzungsschnittstelle.
Auf die Eingabeaktionen des Benutzers reagiert das System also mit entsprechenden Aktionen. Deswegen werden derartige Systeme auch als reaktive Systeme bezeichnet. Die erwähnte Trennung zwischen Interaktion und Funktion gilt
als wichtiges Architekturprinzip interaktiver Softwaresysteme (vgl. Züllighoven
1998, S. 234). In der industriellen Praxis und in Forschung und Lehre werden solche Systeme heutzutage üblicherweise nach dem objektorientierten Ansatz entwickelt.
Die Funktionskomponente realisiert die fachliche Funktionalität. Die Interaktionskomponente realisiert den Datentransfer und präsentiert zusätzlich den jeweils aktuellen Zustand der Funktionskomponente. Allerdings werden Details
bestimmter systeminterner Abläufe der Datenverarbeitung, insbesondere implementierungstechnische Einzelheiten, nach außen hin verborgen. Ein Benutzer ist
normalerweise unmittelbar an den funktionalen Ergebnissen und weniger an den
Details des Berechnungsprozesses interessiert. Er betrachtet das System als Blackbox. Dagegen haben Systementwickler und -analytiker eine erweiterte Sicht: Als
Konstrukteure und Erbauer müssen sie neben den anwendungsfachlichen Aspekten die internen Eigenschaften und Verhaltensweisen aller Systemkomponenten
1
KAPITEL 1. EINLEITUNG
2
kennen (vgl. Züllighoven 1998, S. 92). Für sie stellt sich das System als Whitebox dar.
Untersuchungen konkreter Benutzerreaktionen im Umgang mit dem System
fallen in den Forschungsbereich der Softwareergonomie. Hinsichtlich des Softwaresystems ist dabei hauptsächlich dessen Interaktionskomponente von Interesse. Im Gegensatz dazu wird in dieser Diplomarbeit die Entwicklerseite betrachtet.
Der Fokus richtet sich auf die konkreten internen Systemstrukturen und das dort
beobachtbare Programmgeschehen.
In objektorientierten Systemen sind die dynamischen Abläufe statisch nur
schwer nachvollziehbar. Die Dynamik entsteht durch den Versand von Botschaften. Aus diesem Grund ist z.B. die Anwendung manueller Prüftechniken sehr
kompliziert (vgl. Liggesmeyer 2002, S. 392). So entsteht der Wunsch nach geeigneter Werkzeugunterstützung, insbesondere dann, wenn der Entwickler das Zielsystem nicht kennt.
Der objektorientierte Ansatz ist ein Konzept zur Softwareentwicklung, das für
die Analyse, den Entwurf und die Implementierung eine durchgängige Arbeitsweise ohne Strukturbruch zwischen den einzelnen Phasen ermöglicht. Zudem bietet der Ansatz ein klares Paradigma zur Problemanalyse (vgl. Liggesmeyer 2002,
S. 392). In der Analyse- und Entwurfsphase werden Modelle des Anwendungsbereiches bzw. des Softwaresystems konstruiert, die in der Implementierungsphase
softwaretechnisch realisiert werden.
Hinsichtlich des Testens objektorientierter Programme gelten die gleichen Anforderungen wie beim Testen konventioneller Programme. Unter anderem sind
geeignete Testdaten zu erstellen und Ablaufpfade zu verfolgen. Der Test ist dann
ein Soll-Ist Vergleich, mit dem das tatsächlich gemessene Programmverhalten mit
der vorgegeben Spezifikation verglichen wird. (vgl. Sneed und Winter 2002, S.
22)
Aus der geschilderten Problematik lässt sich der Bedarf eines Werkzeuges ableiten, das das konkrete Laufzeitverhalten eines objektorientierten Programms geeignet aufzeichnet. Ein solches Werkzeug hätte über das Testen hinaus verschiedene Anwendungsmöglichkeiten:
• Bei vorhandener Spezifikation kann das gemessene Verhalten mit dem spezifizierten Verhalten verglichen werden.
• Bei nicht vorhandener Spezifikation können zumindest Teile davon durch
einen Reverse Engineering Prozess wiederhergestellt werden.
KAPITEL 1. EINLEITUNG
3
• Unterstützung bei der Einarbeitung in nicht selbstentwickelte Komponenten, die wiederverwendet werden sollen, z.B. Rahmenwerke.
• Durch Erfassung der Aktivitäten der Programmkomponenten kann eine Abdeckung gemessen werden, die die Identifikation besonders häufig oder gar
nicht genutzter Komponenten ermöglicht.
• Mit Verfolgung der Interaktionen bestimmter Programmkomponenten kann
der Softwareintegrationstest unterstützt werden.
• Durch die ausführliche Erfassung interner Programmvorgänge lassen sich
spezielle Benutzungsprofile erstellen, die im Rahmen einer ergonomischen
Evaluation mit den zuvor festgehaltenen Absichten des Benutzers verglichen werden können.
1.1
Ziel der Arbeit
Das Ziel dieser Diplomarbeit ist die Nachbildung bzw. Verfolgung der systeminternen Vorgänge einer objektorientierten Anwendung. Es wird ein System konzipiert und prototypisch umgesetzt, das die Analyse des internen Programmverhaltens unterstützt. Durch ausführliche Laufzeitaufzeichnungen relevanter Programmaktivitäten werden Ablaufprotokolle erstellt. Auf Basis dieser Ablaufprotokolle kann das Programmverhalten analysiert und bewertet werden. Im Rahmen
dieser Arbeit werden folgende, vereinfachende Randbedingungen gesetzt:
• Es werden ausschließlich objektorientierte Programme behandelt.
• Aus der Menge möglicher Anwendungsbereiche wird insbesondere das Testen von Software als Hauptanwendungsbereich betrachtet.
• Die prototypische Umsetzung erfolgt mit der Programmiersprache Java und
auch als Zielsysteme kommen ausschließlich Javaprogramme in Frage.
• Der Quellcode des Zielsystems soll nicht manipuliert werden, weil er z.B.
nicht verfügbar ist und die Komponenten des Zielsystems nur in binärer
Form vorliegen. Außerdem wird dadurch die Abhängigkeit vom Zielsystem
reduziert.
1.2
Aufbau der Arbeit
Im Kapitel 2 wird der Problembereich näher erläutert. Es werden objektorientierte
Systeme charakterisiert, und es wird eine Auswahl verschiedener Arbeitssituationen gezeigt, mit denen Softwareentwickler typischerweise konfrontiert werden.
KAPITEL 1. EINLEITUNG
4
Es wird der Bedarf eines Systems zur Programmverfolgung deutlich gemacht.
Gleichzeitig werden potenzielle Einsatzgebiete eines entsprechenden Werkzeuges gezeigt, wobei wir uns im Rahmen der Diplomarbeit hauptsächlich auf den
Anwendungsbereich des Testens konzentrieren werden.
In Kapitel 3 werden Möglichkeiten und Hilfsmittel diskutiert, mit denen der
dynamische Ablauf eines Programms verfolgt und beobachtet werden kann. Dabei
wird der Einsatz von Debuggern und die Protokollierung des Programmablaufes
behandelt. Anschließend wird die These aufgestellt und begründet, dass es für
die ausführliche Ablaufverfolgung objektorientierter Programme nicht erforderlich ist, die Sourcen zu manipulieren.
Kapitel 4 behandelt die Modellierung komplexer Systeme. Es werden der Zusammenhang der Modellierungssprache UML und der Modellbeschreibungssprache XMI sowie deren Eigenschaften umrissen. Abschließend wird eine Vision
entwickelt, wie die Modelle durch Programmverfolgung gewonnen und zum Testen benutzt werden können.
In Kapitel 5 wird der Prototyp vorgestellt. Durch diesen soll belegt werden,
dass der hier vorgeschlagene Ansatz praktikabel ist. In je einem eigenen Abschnitt
werden die Anforderungen formuliert, der Systementwurf vorgestellt und implementierungstechnische Aspekte behandelt.
Die Benutzung und Bewertung des Prototypen erfolgt in Kapitel 6 anhand
eines Anwendungsfallbeispiels. Das Beispiel zeigt, wie mit dem Prototypen die
Ausführung eines Testfalls mit dem Testframework JUnit beobachtet und aufgezeichnet werden kann.
Vor der abschließenden Zusammenfassung und dem Ausblick werden im Schlussteil ähnliche oder verwandte Ansätze vorgestellt.
Kapitel 2
Problembereich
Im Folgenden werden die für diese Diplomarbeit relevanten Aspekte objektorientierter Systeme dargestellt. Anschließend wird eine Auswahl typischer Arbeitssituationen beschrieben, in denen Softwareentwickler mit objektorientierten Systemen konfrontiert werden und welche Schwierigkeiten dabei auftreten. Schließlich
wird daraus der Bedarf eines unterstützenden Werkzeuges formuliert, mit dem das
Verhalten eines komplexen objektorientierten Softwaresystems beobachtet werden kann.
2.1
Merkmale und Eigenschaften objektorientierter
Systeme
“Objekte sind auch die zentralen Elemente unserer kognitiven Fähigkeiten. Das Verfahren, Problemstellungen in Form von Objekten und
Botschaften zu interpretieren, kommt den menschlichen Fähigkeiten
sehr entgegen.” (Mitterndorfer 1997, S. 3)
Objektorientierte Softwaresysteme bestehen aus mehreren miteinander kooperierenden Komponenten. Das Systemverhalten wird vom Einzelverhalten jeder
Komponente, aber auch von deren Zusammenwirken geprägt. Das System besteht
aus einem statischen und einem dynamischen Teil. Der statische Teil enthält Klassen und deren Beziehungen, der dynamische Teil wird durch einen Nachrichtenmechanismus realisiert. Die vorliegende Diplomarbeit konzentriert sich auf den
dynamischen Teil. Dieser wird jedoch in seinen Möglichkeiten maßgeblich von
der statischen Struktur geprägt. Aus diesem Grund muss die statische Struktur
miteinbezogen werden.
5
KAPITEL 2. PROBLEMBEREICH
2.1.1
6
Kapselung
Basis des objektorientierten Ansatzes ist die Zusammenfassung von Datenelementen und Operationen auf diesen Daten zu Objekten. Auf die Datenelemente
wird nicht direkt zugegriffen, die Schreib- und Lesezugriffe erfolgen durch die
Operationen. Die jeweils aktuellen Werte der Datenelemente bilden den Zustand
des Objektes. Die Repräsentation des Objektzustandes und die Implementierung
der Operationen sind nach außen gekapselt. Objekte stellen für den Zugang zu
den Operationen eine Schnittstelle bereit. Die statische Strukturbeschreibung der
Objekte und die Implementierung der Operationen erfolgen durch Klassen. Das
Prinzip der Kapselung wird auch als Geheimnisprinzip oder Information-Hiding
bezeichnet (vgl. Parnas 1972).
2.1.2
Message Sending
Klassen gehören zum statischen Teil einer objektorientierten Anwendung. Der dynamische Teil der Anwendung wird durch Objektinteraktionen realisiert. Objekte
interagieren, indem sie sich Botschaften senden und empfangen. Das Senden einer Botschaft an ein Zielobjekt löst die Ausführung der zugehörigen Operation
auf diesem Zielobjekt aus. Hierfür muss die Botschaft interpretiert, die zugehörige Operation aus der Schnittstelle ausgewählt und dann an den Aufruf gebunden
werden. Die Bindung erfolgt zur Laufzeit und ist Aufgabe des Empfängers. Der
Vorgang wird als dynamische Bindung oder Late-Binding bezeichnet. Innerhalb
der Operation besteht Zugriff auf die Daten, so dass Objekte zustandsabhängig
agieren können. Zu einer Botschaft gehören immer ein Sender, ein Empfänger und
die Aufrufargumente. Aufgrund des zustandsabhängigen Verhaltens zählen Sneed
und Winter den Objektzustand zum Zeitpunkt des Aufrufes zum Argumentbereich
einer Operation hinzu (vgl. Sneed und Winter 2002, S. 23).
Objekte sind die bestimmenden Programmeinheiten zur Laufzeit. Das globale Programmverhalten ist gewissermaßen eine Abbildung des Botschaftenflusses
zwischen den Objekten.
2.1.3
Beziehungen
Benutzt-Beziehung Um eine Nachricht absetzen zu können, muss der Sender
den Empfänger kennen. Nur dann kann er dessen Funktionalität benutzen, so dass
eine Benutzt-Beziehung entsteht (vgl. Züllighoven 1998, S. 40). Es ist Aufgabe des Klassen- und Objektentwurfes, geeignete Beziehungen zwischen den Objekten zu finden und herzustellen, indem die dienstnutzenden Objekte mit entsprechenden Verweisen auf diensterbringende Objekte ausgestattet werden. Die
KAPITEL 2. PROBLEMBEREICH
7
Benutzt-Beziehungen werden auch in der konventionellen Programmierung beispielsweise durch den Modulimport verwendet.
Vererbung Die Vererbungsbeziehung ist ein wesentliches Merkmal des objektorientierten Ansatzes, der eine wichtige Rolle für die Wiederverwendung von
Klassen spielt. In einer Vererbungsbeziehung erbt die Unterklasse die Daten und
Operationen ihrer Oberklasse, so dass Objekte der Unterklasse wie Objekte der
Oberklasse agieren können.1 In der Klassenbeschreibung der Unterklasse wird
die Oberklasse statisch angegeben. In der Unterklasse können weitere Operationen hinzugefügt werden. Es ist aber auch möglich, Operationen der Oberklasse zu
überschreiben, indem Operationen mit gleicher Signatur implementiert werden.
Eine Oberklasse kann mehrere Unterklassen haben. Gemeinsame Funktionaliät
mehrerer Klassen kann dadurch in eine gemeinsame Oberklasse herausgezogen
werden. Die Funktionalität der Oberklasse wird also mehrfach in ihren Unterklassen wiederverwendet.
2.1.4
Polymorphie
Eng mit der Vererbung verknüpft, aber nicht darauf beschränkt, ist das Konzept
der Polymorphie. Das Konzept erlaubt die Bindung einer Referenz an Objekte
unterschiedlicher Klassen. Da Referenzen zum Senden von Nachrichten benutzt
werden, kann ein Sender dieselbe Nachricht an Objekte unterschiedlichen Typs
senden. Der Sender benötigt keine Kenntnisse darüber, wie der Empfänger einer
Botschaft die zugehörige Operation findet und ausführt.
2.2
Situation des Entwicklers
“Ein wesentlicher Vorteil der objektorientierten Softwareentwicklung
liegt darin, daß ihre Konzepte - insbesondere das Vererbungskonzept
- die Wiederverwendung vorhandener Klassen und Subsysteme unterstützt.” (Balzert 1996, S. 864)
Es gibt eine Reihe typischer Arbeitssituationen, in denen ein Entwickler der
Aufgabe gegenübersteht, die Strukturen und Abläufe bereits bestehender Softwaresysteme verstehen zu müssen. Nachfolgend eine Auswahl:
1 Je nach Umsetzung der Vererbungsbeziehung durch die verwendete Programmiersprache
können bestimmte Dateneinheiten und Operationen von der Vererbung ausgeschlossen werden.
Auf die unterschiedlichen Sichtbarkeitskonzepte wird hier nicht näher eingegangen.
KAPITEL 2. PROBLEMBEREICH
8
• Testen von Softwaresystemen
Verschiedene Merkmale und Eigenschaften objektorientierter Programme
erschweren das Testen der Software. Die Abläufe in einer objektorientierten Software sind oft schwerer verständlich als bei einer rein funktionalen
Software. Verursacht wird diese Komplexität durch die tief verschachtelten Interaktionspfade der kommunizierenden Objekte. Der Kontrollfluss der
Applikation ist über viele Objekte verteilt, die sich zudem zustandsabhängig
verhalten (vgl. Liggesmeyer 2002, S. 392). Insbesondere bei Anwendung
strukturorientierter Testverfahren und Messung der Testüberdeckung, sind
diese Interaktionswege nachzuvollziehen.
• Nutzung von Klassenbibliotheken und Frameworks
Nach dem sogenannten Offen-Geschlossen-Prinzip bietet eine Klasse einerseits als geschlossener Baustein Dienstleistungen nach außen an und ist
gleichzeitig durch den Vererbungsmechanismus offen für Erweiterungen.
Auf dieser Basis entstehen Klassenbibliotheken als Sammlungen hierarchischer Klassenstrukturen, deren Elemente wiederverwendet oder erweitert werden können. Elemente der Klassenbibliothek bieten Dienste an, deren Nutzung keinen Einfluss auf den Kontrollfluss der Applikation hat. Im
Gegensatz dazu bestehen Frameworks aus einer Sammlung kooperierender
Klassen, die den Kontrollfluss der Anwendung vorgeben. (vgl. Züllighoven
1998, S. 117 f.)
• Instandhaltung und Optimierung
Studien zufolge wird der Anteil an Wartungskosten eines durchschnittlichen
Anwenderunternehmens auf 50-75% des gesamten DV-Budgets geschätzt.
Nicht immer sind Entwicklungsprojekte Neuentwicklungen (vgl. Balzert
1998, S. 664).
• Einbeziehung von Legacy-Systemen
Als Legacy-System werden umfangreiche Software Altsysteme bezeichnet,
die in der Vergangenheit entwickelt wurden und deren zugrundeliegende
Technologie inzwischen überholt ist. Der Nutzwert wird jedoch höher bewertet als der Aufwand für eine Neuentwicklung. Inzwischen existieren eine Reihe objektorientiert entwickelter Legacy-Systeme (vgl. Ducasse 2001,
S. 19 ff.).
• Vorgehensweisen wie z.B. Extreme Programming
Extreme Programming ist eine Vorgehensweise zur Softwareentwicklung,
die den Entwicklungsprozess optimiert. Der Prozess setzt sich aus verschiedenen, teilweise voneinander abhängigen Einzelpraktiken zusammen. Eine
KAPITEL 2. PROBLEMBEREICH
9
davon ist Collective Code Ownership, nach der alle Entwickler für die Entwicklung und Pflege der gesamten Codebasis verantwortlich und zuständig
sind. (vgl. Beck 1999)
• Evaluierung der Benutzung von Anwendungsprogrammen
Neben den extern sichtbaren Reaktionen der Software auf Benutzereingaben ist die Frage von Interesse, welche systeminterne Dynamik durch die
Eingaben hervorgerufen wird. Darüber hinaus kann man das entsprechende
Systemverhalten mit der Intention des Benutzers vergleichen.
Je nach zugrunde liegender Technologie und Komplexität des Zielsystems ist
die Erschließung des zu verwendenden Systems eine problematische Aufgabe,
die viel Zeit und damit Ressourcen in Anspruch nehmen kann. Ursachen hierfür
können sein:
• Unzureichend vorliegende Spezifikation bzw. Dokumentation.
• Hoher Komplexitätsgrad durch häufige inkrementelle Weiterentwicklung
über größere Zeiträume, wodurch z.B. Codeduplizierungen entstehen.
• Ungeeignete Systemstrukturen als Folge von sich zwischenzeitlich geänderten Anforderungen.
• Fehlende Testfälle.
• Die industrielle Entwicklung objektorientierter Systeme konnte sich etablieren, ohne dass geeigente Qualitätsicherungskonzepte beachtet wurden. Diese wurden erst später etabliert. (vgl. Winter 1999, S. 27)
2.3
Zusammenfassung
Es wurde eine Reihe von Situationen gezeigt, in denen der Softwareentwickler
sich im Nachhinein die dynamische Struktur einer Anwendung zu weiten Teilen
erschließen muss. Je nach Situation, ist auch die statische Struktur miteinzubeziehen. Diese ist einfacher zu verstehen, wenn sich der Entwurf eng an dem fachlichen Anwendungsbereich orientiert. Zudem liegt der statische Entwurf meistens
vollständig als Klassendiagramm vor.
Der Entwurf der dynamischen Struktur erfolgt in der Regel nur für ausgewählte Interaktionen. Der Aufwand zur vollständigen Modellierung der dynamischen
Struktur ist bereits für kleine objektorientierte Programme unvertretbar hoch. Mit
geeigneten Hilfsmitteln und Werkzeugen kann ein Wartungsprogrammierer die
KAPITEL 2. PROBLEMBEREICH
10
Dynamik am System ablesen, so dass auf die vollständige Spezifikation der Systemdynamik verzichtet werden kann. Offen bleibt damit jedoch die Frage, gegen
welche Soll-Vorgaben das Systenverhalten dann getestet wird.
Kapitel 3
Programmverfolgung
Um im Rahmen einer dynamischen Analyse das Verhalten eines Programms nachvollziehen zu können, muss der Ablauf systeminterner Vorgänge beobachtbar gemacht werden. Bei der Untersuchung wird das Zielsystem mit konkreten Eingabedaten versehen und ausgeführt. Durch Beobachtung des Laufzeitverhaltens
werden dabei bestimmte Systemvorgänge erfasst.
Der Vorgang des Beobachtens eines Programms zur Laufzeit wird in der Literatur häufig als Tracing (Reiss und Renieris 2000; Böcker und Herczeg 1990b)
oder auch als Tracking (Renaud 2000) bezeichnet. In dieser Diplomarbeit wird
mit dem Begriff Systemtrace die Menge der zur Ausführungszeit gesammelten
Informationen über den Programmablauf bezeichnet. Der Systemtrace enthält die
Vorgänge des Programms in der Reihenfolge ihres Auftretens.
Renaud unterscheidet zwischen invasiven und nichtinvasiven Tracing Verfahren. Als invasives Tracing werden solche Verfahren bezeichnet, die die Zielapplikation manipulieren, um die Programmabläufe verfolgen zu können. Entsprechend verfolgen nichtinvasive Verfahren das Programm, ohne es vorher zu manipulieren. Nichtinvasive Programmverfolgung hat den Vorteil, dass sie gegenüber
den Strukturen der Zielapplikation unabhängiger ist. (vgl. Renaud 2000, S. 449)
Im Folgenden werden zwei in der Praxis häufig anzutreffende Konzepte und
Technologien zur Programmbeobachtung kurz vorgestellt und diskutiert. Hierbei
handelt es sich um das sogenannte Debugging und um das Protokollieren. Anschließend wird der in dieser Diplomarbeit verwendete Ansatz zur Programmverfolgung vorgestellt.
11
KAPITEL 3. PROGRAMMVERFOLGUNG
3.1
12
Debuggen
Informationen über das konkrete Programmverhalten liefert beispielsweise das
Programm selbst, wenn entsprechende Ausgabeanweisungen an ausgewählten Programmstellen eingefügt werden. Mit speziellen Werkzeugen, sogenannte Debugger, kann ein laufendes Programm aber auch direkt in seiner Laufzeitumgebung
beobachtet werden, ohne dass dieses im Quellcode berücksichtigt werden muss.
Je nach eingesetzter Programmiersprache ist es lediglich erforderlich, den Übersetzer zusätzliche Informationen in das binäre Programm einfügen zu lassen.
Ein Debugger ist ein interaktives Programm, das eine Umgebung zur Verfügung
stellt, in der der Ablauf des Zielsystems manuell kontrolliert werden kann. Das
Zielsystem wird an bestimmten Stellen angehalten und wieder fortgesetzt. Solche
Haltestellen (Breakpoints) können auch mit Bedingungen verknüpft sein. Daneben sind die aktuellen Werte aller Variablen sichtbar und veränderbar, so dass der
aktuelle Programmzustand untersucht bzw. verändert werden kann.
Mit Debuggern kann man einen Einblick in das Programmgeschehen bekommen, ohne den Code vorher aufwendig zu erweitern (vgl. Zeller und Krinke 2000,
S.201ff). Debugger zählen zu den Standardwerkzeugen eines Entwicklers. Aufgrund ihrer engen Verwebung mit dem Laufzeitsystem erlauben sie eine systemnahe Programmanalyse auf sehr niedrigem Abstraktionsgrad.
3.2
Protokollieren
Es gibt Situationen, in denen Debugger nicht oder nur ungenügend dazu geeignet
sind, Programmvorgänge zu verfolgen. Dies ist z.B. dann der Fall, wenn die beobachteten Programmereignisse für eine spätere Auswertung aufgezeichnet bzw.
aufgehoben werden sollen. Solche Protokolle können hilfreich für die Wartung
von Systemen in Produktionsumgebungen sein.
Das Aufzeichnen von Informationen, mit denen Aussagen über das systeminterne Verhalten gemacht werden können, wird als Logging bezeichnet. Die aufzuzeichende Information wird von Ausgabeanweisungen geliefert, die an entsprechenden Stellen im Quellcode eingefügt sind. Das Einfügen solcher Anweisungen
in den Code wird auch als Instrumentierung bezeichnet und kann je nach Anwendung automatisiert erfolgen (vgl. Balzert 1998, S.395).
Zu den Logginginformationen zählen typischerweise aktuelle Variableninhalte oder Funktionsnamen oder -argumente. In der Regel wird unmittelbar vor der
KAPITEL 3. PROGRAMMVERFOLGUNG
13
Ausgabe ein Betriebsmodus abgefragt, so dass sie in Abhängigkeit des aktuellen
Betriebsmodus unterdrückt werden kann.
Loggingframeworks
Die manuelle Instrumentierung des Quellcodes kann durch den Einsatz von LoggingFrameworks unterstützt werden. Für verschiedene Programmiersprachen sind entsprechende Bibliotheken verfügbar. In Java ab Version 1.4 ist die Loggingfunktionalität in die Sprache integriert. Im Allgemeinen bietet ein Loggingframework
folgende Funktionen:
• Verknüpfung eines Protokolleintrages mit einer Prioritätsstufe.
• Skalierung der Protokollausgaben durch Konfiguration des Loggers, so dass
Einträge unterhalb einer bestimmten Prioritätsstufe unterdrückt werden.
• Unterstützung verschiedener Ausgabemedien, wie z.B. Standardausgabekanal oder Datei.
• Automatisches Hinzufügen von Informationen wie Zeitstempel oder aktueller Funktionsname.
• Formatierung der Ausgaben.
3.3
Fazit
Es wurde gezeigt, dass mit Debuggern sehr mächtige und flexible Werkzeuge zur
Programmbeobachtung zur Verfügung stehen. Mit Debuggern können praktisch
alle vorhandenen Informationen im laufenden Programm betrachtet und teilweise
sogar verändert werden. Vorteilhaft ist hierbei, dass auf eine Instrumentierung des
Quellcodes vollständig verzichtet werden kann.
Innerhalb des Debuggers wird das Zielprogramm an zuvor eingefügten Haltestellen angehalten, so dass an der Stelle die aktuellen Werte der Variablen inspiziert und ggf. geändert werden können. Der weitere Kontrollfluss des Programms kann anschließend schrittweise durch den Benutzer weiterverfolgt werden. Primärer Einsatzzweck von Debuggern ist die konkrete Fehlersuche in Programmen. Sie unterstützen den Entwickler bei der manuellen Inspektion des Programmflusses. Der Programmablauf wird dabei aber nicht mit aufgezeichnet.
Es kann jedoch sinnvoll oder sogar erforderlich sein, dass ein Programm Informationen über interne Vorgänge mitprotokolliert, so dass ein bestimmter Ablauf
KAPITEL 3. PROGRAMMVERFOLGUNG
14
auch im Nachhinein reproduziert werden kann. Um einen solchen Systemtrace zu
erzeugen werden Loggingframeworks eingesetzt. An ausgewählten Programmstellen erfolgen Ausgaben über den internen Programmzustand in das Protokoll.
Die entsprechenden Anweisungen sind im Quellcode einzufügen.
Durch diese Instrumentierungen werden Informationen des Ablaufes automatisch durch das Programm geliefert. Der Detaillierungsgrad der Informationen
kann von außen z.B. durch Konfigurationsdateien gesteuert werden, ohne das Programm neu kompilieren zu müssen.
Als Nachteile des Loggings werden folgende Punkte angeführt:
• Die im Quellcode enthaltenen Ausgabeanweisungen müssen bei Programmänderungen u.U. angepasst werden.
• Der Entwickler muss immer wieder neu entscheiden, zu welchem Zeitpunkt
welche Informationen zu protokollieren sind.
• Es gibt keine allgemeinen Standards für die Protokolle.
In dieser Diplomarbeit wird ein Ansatz der Programmbeobachtung verfolgt,
der die Vorteile der beiden beschriebenen Technologien verbindet. Die Idee dabei
ist, aus der Laufzeitumgebung eines objektorientierten Programms alle relevanten
Informationen zu gewinnen, mit denen das Verhalten der Anwendung ausführlich
mitprotokolliert werden kann. Der Quellcode wird dabei nicht berücksichtigt bzw.
manipuliert.
Offensichtlich ist der gesamte Nachrichtenverkehr durch das Versenden der
Botschaften der Ort, an dem die Interaktionen der Objekte vollständig beobachtet
werden kann (siehe Abschnitt 2.1). Hört man den gesamten Nachrichtenverkehr
ab, so kann systemweit beobachtet werden, welcher Sender welchem Empfänger
welche Nachricht sendet. Damit kann ein Abbild des Programmverhaltens erzeugt
werden.
KAPITEL 3. PROGRAMMVERFOLGUNG
15
Die Realisierung eines solchen Ansatzes bietet folgende Vorteile:
• Die Beobachtung des Botschaftenflusses zwischen den Objekten ist ein systematischer Ansatz, den Programmablauf zu verfolgen.
• Die Verfolgung geschieht unabhängig von der konkreten Systemarchitektur.
• Die Vorgänge können erfasst und protokolliert werden. Dadurch stehen sie
für spätere Auswertungen zur Verfügung.
• Der Quellcode wird nicht berücksichtigt, so dass keine Instrumentierungen
gepflegt werden müssen und die Abhängigkeit zu den Strukturen der Zielanwendung gering bleibt.
• Ein solches System kann als Testwerkzeug eingesetzt werden, indem der
aufgezeichnete Botschaftenfluss mit dem geplanten Botschaftenfluss verglichen wird.
Kapitel 4
Modellierung komplexer Systeme
“Anwendungssoftware ist ein Mittel zum Zweck, um fachliche Aufgaben in einem oder mehreren Anwendungsbereichen zu erledigen.
Dazu werden vorhandene oder neue Problemlösungsstrategien durch
Software realisiert.” (Züllighoven 1998, S. 124)
Nichttriviale Softwareanwendungen sind komplexe Systeme. Nach Lehmans
Gesetzen werden sie zunehmend nutzlos, wenn sie nicht kontinuierlich an die sich
ändernden Umgebungsbedingungen angepasst werden. Die vorgenommenen Anpassungen führen zu einer Steigerung der Komplexität, wenn nicht zusätzlicher
Aufwand investiert wird, dies zu verhindern. (vgl. Lehman 1996)
In der Softwaretechnik werden verschiedene allgemeine Prinzipien und Methoden angewendet, die dabei helfen sollen, die Komplexität der Systeme zu beherrschen. So ist zum Beispiel das Abstraktionsprinzip eines der wichtigsten Prinzipien der Software-Technik. Balzert spricht “... anstelle von Abstraktion auch von
Modellbildung. Durch das Abstrahieren vom Konkreten macht man ein Modell
der realen Welt, d.h. das Modell repräsentiert die reale Welt durch sein charakteristisches Verhalten.” (Balzert 1998, S. 559)
Seit Softwaretechnik als eigenständige Disziplin besteht (Ende der sechziger
Jahre), hat es sich bewährt, von maschinennahen Darstellungen zu abstrahieren
und näher am Anwendungsbereich zu modellieren. Inzwischen ermöglichen objektorientierte Programmiersprachen prinzipiell die bruchlose Transformation eines objektorientierten Anwendungsmodells in ein ablauffähiges Programm. (vgl.
Kilberth u. a. 1994, S. 7f.)
16
KAPITEL 4. MODELLIERUNG KOMPLEXER SYSTEME
4.1
17
Strukturen komplexer Systeme
Im Vergleich zu konventionellen funktionsorientierten Systemen weisen objektorientierte Systeme eine verbesserte Architektur auf. Die in Abschnitt 2.1 dargestellten Eigenschaften objektorientierter Systeme, insbesondere die Kapselung
und das Geheimnisprinzip, führen zu einer stärkeren Modularisierung. Durch die
höhere Anzahl einzelner Module entsteht jedoch neue Komplexität, weil sich
dadurch mehr intermodulare Abhängigkeiten und Interaktionsmöglichkeiten ergeben. (vgl. Sneed und Winter 2002; Winter 2000, S. 25)
Der Kontrollfluss eines objektorientierten Programms wird nicht zentral gesteuert. Die Systemdynamik kommt durch das Zusammenspiel der einzelnen Module bzw. Objekte zustande. Die statische Struktur des Systems gibt die Schnittstellen und Beziehungen der Objekte vor. Die dynamische Struktur ergibt sich
dann aus dem Verhalten der Objekte.
Statische Strukturen liegen ab einem bestimmten Zeitpunkt vor und bleiben
von da an erhalten. Dynamische Strukturen entstehen zur Laufzeit des Systems.
(vgl. Balzert 1996, S. 567 f.) Zur Erfassung der dynamischen Struktur muss das
System ausgeführt werden. Mittels Abstraktion kann dabei ein (statisches) Modell der dynamischen Struktur gewonnen werden, indem die Ausführung geeignet
verfolgt und aufgezeichnet wird.
4.2
Modellierung
Für die Modellierung komplexer objektorientierter Systeme wird von der Object
Management Group (OMG) die Unified Modeling Language (UML) spezifiziert.
Hauptziel ist dabei, unabhängig von konkreten Anwendungsbereichen und Programmiersprachen, geeignete Modellierungselemente und deren semantische Zusammenhänge zu definieren. Daneben wird eine einheitliche grafische Notation
der Modelle festgelegt. Durch Systemmodellierung mit der UML können Baupläne für objektorientierte Softwaresysteme erstellt werden. (vgl. OMG 2001, S.
13)
Die UML definiert verschiedene Diagrammtypen, mit denen sowohl die statische Struktur (Klassendiagramm), als auch die dynamische Struktur (Sequenz-,
Kollaborations-, Zustands- oder Aktivitätsdiagramm)1 modelliert werden können.
1 Es werden nicht alle Diagrammtypen genannt. Auf eine Darstellung der einzelnen Diagramme
wird verzichtet. Hierfür sei auf die Fachliteratur verwiesen.
KAPITEL 4. MODELLIERUNG KOMPLEXER SYSTEME
18
Die visuelle Darstellung der einzelnen Diagramme basiert auf jeweils wohldefinierten semantischen Modellen, die mit graphentheoretischen Mitteln formal
beschreibbar sind. Darin wird ein großer Vorteil gesehen, weil dadurch die Modelle auf semantischer Ebene weitgehend maschinell verarbeitet werden können.
Für die semantische Beschreibung der Modelle spielt die im nächsten Abschnitt
vorgestellte Beschreibungssprache XMI (XML-Metadata Interchange) eine große
Rolle.
4.3
Modellaustauschformat XMI
Die Ausführungen in diesem Abschnitt basieren hauptsächlich auf einen Vortrag
M. Jeckles, die Präsentation ist unter (vgl. Jeckle 2001) zu finden. Mit dem von
der OMG verabschiedeten XMI-Standard (vgl. OMG 2000) ist es möglich, die
verschiedenen UML-Modelle mit einem auf XML basierenden Dokument zu beschreiben. Der XMI Standard definiert eine Document Type Definition (DTD) für
alle UML-Modelle. Damit wird ein allgemeines einheitliches Beschreibungsformat festgelegt, mit dem die UML-Modelle zwischen den unterschiedlichen Werkzeugen ausgestauscht werden können. Eine Vielzahl am Markt erhältlicher CASETools unterstützt inzwischen den Im- und Export von XMI-Dateien.
Im Wesentlichen basiert die Modellbeschreibung eines gegebenen Modells M
auf der Überführung in das Metamodell M 0 , so dass M in M 0 als konkrete Instanz enthalten ist. Das Metamodell wird dann mit der konkreten Instanz in eine
Baumstruktur tranformiert und als XML Datei gespeichert. Voraussetzung für den
Modellaustauch mit XMI ist somit die Existenz eines Metamodells. Im aktuellen
UML Standard wird hierfür explizit ein UML-Interchange Metamodel definiert.
(vgl. OMG 2001, S. 5-1 ff.)
Ein mit XMI beschriebenes Modell enthält keine standardisierten Informationen zur visuellen Darstellung, weil diese in der aktuellen Version 1.4 des UMLInterchange Metamodells nicht definiert sind. Enstprechende Layoutinformationen
sind für den kommenden UML Standard 2.0 vorgesehen.
Neben dem werkzeugübergreifenden Modellaustausch bietet das standardisierte Beschreibungsformat die Möglichkeit, die Modelle zu validieren und z.B.
Metriken zu erstellen. Auch die Versionsverwaltung wird durch das textbasierte
Dateiformat erleichtert.
KAPITEL 4. MODELLIERUNG KOMPLEXER SYSTEME
19
Insbesondere in Hinblick auf das dynamische Testen objektorientierter Systeme sei hier angemerkt, dass die standardisierte Beschreibung der Modelle deren
Vergleichbarkeit ermöglicht.
4.4
Zusammenfassung
Es wurde gezeigt, dass das Prinzip der Abstraktion bzw. der Modellierung eine
Maßnahme ist, um die Komplexität von Softwaresystemen zu beherrschen. Mit
der UML liegt eine standardisierte Modellierungssprache vor, mit der objektorientierte Systeme modelliert werden können. Die Modellierung erfolgt dabei unabhängig vom konkreten Anwendungsbereich und der Programmiersprache. Mit
UML-Diagrammen können Baupläne für das zu entwickelnde Softwaresystem erstellt werden.
Diese Baupläne spezifizieren das Softwaresystem in seinen Strukturen. Im
Hinblick auf das Testen definiert die Spezifikation das erwartete Soll-Ergebnis.
Die statische Struktur des Systems wird in der Regel vollständig mit UML-Klassendiagrammen modelliert bzw. spezifiziert. Dagegen erfolgt die Spezifikation der
dynamischen Struktur aufgrund der hohen Komplexität nur für bestimmte Systembereiche. Eine vollständige Spezifikation der dynamischen Struktur ist unter
einem vertretbaren Kosten-Nutzen Verhältnis nicht zu leisten.
Gäbe es für die Modellierung bzw. Spezifikation der dynamischen Struktur
geeignete Werkzeugunterstützung, so könnte das Aufwand-Nutzen Verhältnis zugunsten des Nutzens verschoben werden. Der im Rahmen dieser Diplomarbeit
entwickelte Prototyp wäre für diesen Zweck geeignet.
Durch Ausführung und Beobachtung bestimmter Abläufe des Zielsystems werden die maschinennahen Ereignisse und Vorgänge aufgezeichnet und ergeben so
den Systemtrace für eine bestimmte Interaktionsfolge. Mit entsprechender Aufbereitung der gesammelten Informationen wird durch Abstraktion ein Modell
des dynamischen Ablaufes erstellt. Ein derartiges Modell präsentiert dann das
Ist-Ergebnis des dynamischen Systemverhaltens. Bei Vorliegen einer zughörigen
Spezifikation kann das rekonstruierte Modell mit dem Soll-Wert verglichen werden. Das Modell kann aber auch, nach manueller Analyse und Bewertung, spezifizierenden Charakter erhalten und von nun an als Soll-Vorgabe dienen.
Es wäre ein großer Vorteil, wenn der Vergleich der Modelle maschinell vorgenommen und ausgewertet werden könnte. Dieses ist vermutlich auf der Basis
allgemeiner Standards wie UML und XMI realisierbar, wird im Rahmen der vor-
KAPITEL 4. MODELLIERUNG KOMPLEXER SYSTEME
20
liegenden Diplomarbeit aber nicht weiter verfolgt. Voraussetzung für einen derartigen Test ist die zuverlässige Gewinnung von geeigneten Modellbeschreibungen
auf Basis der beobachtbaren Programmvorgänge. Dieser Aspekt wird im folgenden Verlauf der Arbeit fokusiert und prototypisch realisiert.
Kapitel 5
Prototyp
In diesem Kapitel wird der im Rahmen der vorliegenden Diplomarbeit entwickelte Prototyp vorgestellt. Mit diesem soll die Machbarkeit der in den Kapiteln 3
und 4 dargestellten Ideen und Ansätze zur Programmverfolgung gezeigt werden.
Gleichzeitig soll der Prototyp den Ausgangspunkt eines evolutionären Entwicklungsprozesses bilden, der ggf. auch die Weiterentwicklung der beschriebenen
Ideen und Ansätze umfasst.
Das Einsatzziel des Prototypen ist die Rekonstruktion der Dynamik eines objektorientierten Systems, ohne dabei den Quellcode des Zielsystems zu verwenden. Die Rekonstruktion erfolgt dabei im Rahmen einer dynamischen Analyse, in
der das Zielsystem ausgeführt und zur Laufzeit beobachtet wird. Dabei werden
die Vorgänge auf dem entfernten System erfasst und aufgezeichnet. Der dadurch
entstehende Systemtrace ermöglicht die Analyse und Bewertung des Programmverhaltens.
In den folgenden Abschnitten werden zunächst die Anforderungen an ein solches System ermittelt und festgehalten. Anschließend werden Systemstruktur und
Architektur des Prototypen beschrieben. Abschließend wird auf die Implementierung eingegangen.
5.1
Analyse
“Eine Anforderung ist eine Aussage über eine zu erfüllende Eigenschaft oder zu erbringende Leistung eines Produktes, eines Prozesses
oder der am Prozess beteiligten Personen.” (vgl. Rupp 2001, S. 10)
Das Ziel der Analyse ist die Ermittlung und Dokumentation der Anforderungen an das zu entwickelnde System. Als Ergebnis entsteht eine Spezifikation, die
21
KAPITEL 5. PROTOTYP
22
Abbildung 5.1: Anwendungsfalldiagramm
aus Benutzersicht beschreibt, was das System leistet. Softwareentwicklung geht in
der Regel mit einem Lernprozess einher. Das heißt, dass erst im Entwicklungsverlauf neue Anforderungen erkannt und vorhandene Anforderungen vervollständigt
bzw. verfeinert werden und diese nicht von Anfang an eindeutig und vollständig
bestimmt werden können. (vgl. Züllighoven 1998, S. 128) In diesem Sinne werden im Folgenden die initialen Anforderungen an den Prototypen ermittelt, so dass
die Vision des zu entwicklenden Systems konkretisiert werden kann.
Ein Anwendungsfalldiagramm (siehe Abbildung 5.1) gibt einen Überblick
über ausgewählte Anwendungsfälle des Systems. Nach dessen Erläuterung wird
anhand von Szenarien auf die einzelnen Anwendungsfälle eingegangen. Basierend auf diesen Szenarien werden dann im Hinblick auf den softwaretechnischen
Entwurf die Anforderungen festgehalten.
Das Anwendungsfalldiagramm zeigt die durch den Prototypen zu realisierenden Anwendungsfälle und die systemexternen Akteure bzw. Entitäten. Als Basisanwendungsfall dient der Anwendungsfall “Laufzeitverhalten aufzeichnen”. Üblicherweise interessiert den Benutzer dabei ein spezielles Anwendungsszenario, das
er auf dem Zielsystem ausführen und durch den Prototypen aufzeichnen lassen
möchte.
Eine konkrete Spezialisierung des Basisanwendungsfalles ist z.B. der Anwendungsfall “Dynamisch Testen”. Hier wird das Anwendungsszenario zu einem konkreten Testfall. Getestet wird das Zielsystem, das damit zum Testobjekt wird. Testen ist immer ein Soll-Ist Vergleich, so dass das erwartete Laufzeitverhalten hinreichend spezifiziert sein muss, damit das aufgezeichnete Laufzeitverhalten damit
KAPITEL 5. PROTOTYP
23
verglichen werden kann.
Das gleiche gilt für den Anwendungsfall “Überdeckung messen”. Hier soll eine quantitative Aussage darüber gemacht werden, wie oft welche Operation aufgerufen wird. Mit entsprechenden Informationen können ggf. nicht benutzte und
damit nicht getestete Komponenten identifiziert werden.
In den nachfolgenden Abschnitten werden die drei Anwendungsfälle mit Szenarien konkretisiert. Ein Szenario beschreibt die Interaktionsfolge zwischen dem
Benutzer und dem zu entwickelnden Prototypen aus Sicht des Benutzers. Es wird
auf abstrakter Ebene ermittelt, welche Aktivitäten das Programm leisten soll und
welche Aktivitäten der Benutzer durchführt. Die Darstellung der Szenarien erfolgt
mit UML-Sequenzdiagrammen, die mit Worten ergänzt werden.
5.1.1
Anwendungsfälle
Laufzeitverhalten aufzeichnen
In diesem Anwendungsfall ist es das Ziel des Benutzers, ein bestimmtes Programmverhalten zu beobachten und aufzuzeichnen. Abbildung 5.2 auf Seite 24
zeigt das zugehörige Sequenzdiagramm. Der Benutzer interagiert sowohl mit dem
Prototypen als auch mit dem Zielsystem. Nachfolgend werden die einzelnen Schritte erläutert.
Schritt 1: Der Benutzer lädt das Zielsystem, indem er ein Verzeichnis des lokalen Dateisystems angibt, das die binären Programmdateien enthält. Optional
kann der Benutzer einen Klassenpfad definieren, der Bibliotheken enthält, die das
Zielsystem zur Ausführung benötigt. Nach der erfolgten Eingabe analysiert der
Prototyp die statische Programmstruktur. Die Analyse orientiert sich dabei an der
hierarchischen Organisation in Packages, Klassen und Methoden. Die Struktur
dieser Komponenten wird dem Benutzer am Bildschirm präsentiert.
Schritt 2: Der Benutzer markiert in der Liste der Programmkomponenten diejenigen, an deren Verhalten er interessiert ist. Die Menge aller markierten Komponenten bildet den Beobachtungsfilter. Dieser Filter kann prinzipiell auch leer sein,
in diesem Fall wird keine Komponente beobachtet. Die Markierung kann jederzeit
wieder aufgehoben werden.
Schritt 3: Der Benutzer gibt die Klasse an, die zum Programmstart aufgerufen
werden soll. Mit Abschluss dieses Schrittes liegen alle Informationen vor, um das
Zielsystem starten zu können.
KAPITEL 5. PROTOTYP
Abbildung 5.2: Szenario: Laufzeitverhalten aufzeichnen
24
KAPITEL 5. PROTOTYP
25
Abbildung 5.3: Szenario: Dynamischen Test durchführen
Schritt 4: Der Programmstart des Zielsystems erfolgt über den Prototypen. Dieser erzeugt eine virtuelle Maschine und registriert den in Schritt 2 definierten
Beobachtungsfilter. Anschließend wird die in Schritt 3 angegebene Startklasse
aufgerufen, so dass das Zielsystem jetzt ausgeführt wird und an der Oberfläche
erscheint.
Schritt 5: Der Benutzer führt jetzt die Aktionen direkt auf dem Zielsystem aus.
Dabei werden interne Ereignisse an den Prototypen gemeldet, wenn sie Komponenten betreffen, die im Beobachtungsfilter enthalten sind. Der Prototyp wertet
die ankommenden Ereignisse aus und zeichnet die bei der Auswertung ermittelten Informationen auf.
Schritt 6: Das aktuelle Laufzeitprotokoll kann jederzeit durch den Benutzer in
einer Datei gespeichert werden.
Schritt 7: Der Benutzer beendet das Zielprogramm direkt, indem er die Anwendung regulär beendet (nicht im Sequenzdiagramm dargestellt). Das Zielsystem kann aber auch über den Prototypen beendet werden, indem der Prozess der
virtuellen Maschine des Zielsystems beendet wird.
KAPITEL 5. PROTOTYP
26
Dynamischen Test durchführen
In diesem Anwendungsfall (siehe Abbildung 5.3) ist es das Ziel des Benutzers,
ein gegebenes Anwendungsszenario auf dem Zielsystem auszuführen und das
tatsächliche Laufzeitverhalten mit dem erwarteten zu vergleichen. Es ist daher
erforderlich, dass das erwartete Verhalten hinreichend genau spezifiziert ist.
Schritt 1: Das Laufzeitverhalten der Zielanwendung wird, wie im Anwendungsfall “Laufzeitverhalten aufzeichnen” beschrieben, mit dem Prototypen aufgezeichnet.
Schritt 2: Unter Benutzung des Prototypen exportiert der Benutzer den Systemtrace in eine XML-Datei, die eine Repräsentation eines UML-Sequenzdiagramms
enthält. Die Beschreibung des Diagramms erfolgt mit der XML-Metadata Interchange Language (XMI).
Schritt 3: Der eigentliche Test findet manuell oder mit geeigneten externen
Werkzeugen, z.B. CASE-Tools statt.
Überdeckung messen
In diesem Anwendungsfall ist es das Ziel des Benutzers eine Quantifizierung des
Laufzeitverhaltens zu erhalten. Das ermöglicht eine Identifikation nicht benutzter
und damit nicht getesteter Methoden. Auf eine Darstellung des Sequenzdiagrammes kann hier aufgrund der Ähnlichkeit zum vorherigen Anwendungsfall verzichtet werden.
Schritt 1: Das Laufzeitverhalten der Zielanwendung wird, wie im Anwendungfall “Laufzeitverhalten aufzeichnen” beschrieben, mit dem Prototypen aufgezeichnet.
Schritt 2: Analog zum Anwendungsfall “Dynamisch Testen” wird die Metrik
in eine Datei exportiert.
Schritt 3:
5.1.2
Die Metrik wird manuell ausgewertet.
Anforderungen
Mit den Szenarien wurde die Benutzung des Prototypen aus Anwendersicht beschrieben. Im Hinblick auf den softwaretechnischen Entwurf des Prototypen wer-
KAPITEL 5. PROTOTYP
27
den im Folgenden die Anforderungen auf Basis der Szenarien aufgestellt. Dabei
werden die Anforderungen in zwei Arten unterschieden (vgl. Rupp 2001, S. 146
ff.):
funktional: Leistungsforderungen an Aktivitäten, die das System ausführt, oder
dem Benutzer durch Interaktionsmöglichkeiten anbietet. Funktionale Anforderungen sind in der Regel anwendungsfachlich motiviert.
technisch: Anforderungen, die sich auf Rand- und Umgebungsbedingungen beziehen. Aspekte sind z.B. Programmiersprache, Betriebssysteme, grafische
Fenstersysteme, Datenhaltung, Fremdsysteme usw.
Funktionale Anforderungen
Die funktionalen Anforderungen werden in vier Gruppen eingeteilt, die im Folgenden aufgeführt werden:
I Anforderungen an die Analyse der statischen Struktur
Für die Analyse der statischen Struktur des Zielsystems sind verschiedene
Punkte zu berücksichtigen.
I-1 Die folgenden Artefakte sollen eindeutig identifiziert werden:
•
•
•
•
Klassen
Interfaces
Konstruktoren
Methoden
I-2 Eigenschaften und Sichtbarkeitsbereiche der Artefakte sollen erkannt
und dargestellt werden.
I-3 Hierarchische Beziehungen der Klassen und Interfaces sollen erkannt
und dargestellt werden.
I-4 Die von den Klassen implementierten und von den Interfaces deklarierten Methoden sollen erkannt und dargestellt werden.
II Anforderungen an die Programmverfolgung
II-1 Die Vorgänge auf dem entfernten System sollen in der Reihenfolge
ihres Auftretens erfasst werden.
II-2 Auf Basis der erfassten Vorgänge soll das Verhalten des Zielsystems
rekonstruiert werden können.
KAPITEL 5. PROTOTYP
28
II-3 Aus der Menge der erfassten Vorgänge sollen nachvollziehbare Protokolle erstellt werden können.
III Anforderungen an die Ausführungskontrolle
Es ist mit einer großen Menge an zu verarbeitender Information zu rechnen. Dies wirkt sich negativ sowohl auf das Laufzeitverhalten während der
Aufzeichnung der Vorgänge, wie auch auf die Auswertung der Protokolle aus. Deshalb ist ein Mechanismus erforderlich, mit dem ausschließlich
ausgewählte Bereiche des Zielsystems beobachtet werden können.
III-1 Die in Anforderung I-1 ermittelten statischen Strukturinformationen
werden am Bildschirm angezeigt. Aus dieser Präsentation sollen die
zu beobachtenden Kandidaten auswählbar sein.
III-2 Die Auswahl der zu beobachtenden Komponenten soll zur Laufzeit
veränderbar sein, damit die geeignete Konfiguration durch Ausprobieren ermittelt werden kann.
III-3 Die Ausführung des Zielsystems sollte jederzeit unterbrochen und an
derselben Stelle wieder fortsetzbar sein.
III-4 Wenn eine Startklasse angegeben wurde, dann soll das Zielsystem
durch den Prototypen gestartet und beendet werden können.
III-5 Eine wichtige Funktion ist die globale Deaktivierung bzw. Reaktivierung der Programmverfolgung. Dies ist insbesondere in solchen Situationen nützlich, in denen der Startzustand des zu beobachtenden
Szenarios manuell auf dem Zielsystem herbeizuführen ist, bevor die
eigentliche Untersuchung beginnt.
III-6 Der aktuelle Systemtrace soll jederzeit gelöscht werden können.
IV Anforderungen an die Präsentation und Datenspeicherung
IV-1 Der durch Ausführung des Zielsystems entstehende Systemtrace soll
zur Laufzeit am Monitor verfolgt werden können.
IV-2 Für spätere Auswertungen soll der Systemtrace jederzeit in einer Datei
gespeichert werden können.
IV-3 Auf Basis des aktuellen Systemtraces soll ein Modell der dynamischen
Vorgänge des Zielsystems erstellt und als XML Datei gespeichert werden können. Die Serialisierung erfolgt dabei mit der Modellbeschreibungssprache XMI1 .
1 XML
Metadata Interchange
KAPITEL 5. PROTOTYP
29
IV-4 Zur Messung der Aktivitäten soll eine einfache Metrik erstellt und als
Textdatei gespeichert werden, die den einzelnen Methoden eine Zahl
zuordnet, mit der die Häufigkeiten der Aufrufe gezählt werden.
Technische Anforderungen
• Der Prototyp wird in der Programmiersprache Java Version 1.3.1 unter Linux entwickelt.
• Die Ausführung des Prototypen und die des Zielsystems erfolgen je in der
Java Virtual Machine der Firma Sun, die standardmäßig im Java 2 SDK enthalten ist [URL-Java]. Diese implementiert das JVMDI (Java Virtualmachine Debug Interface), das für die Beobachtung der Vorgänge vorausgesetzt
wird.
• Die Firma Sun stellt zwei Technologien zur Verfügung, die Beobachtung
der Laufzeitvorgänge der JVM ermöglichen:
JVMPI: Das Java Virtualmachine Profiling Interface bietet eine Systemschnittstelle, mit der die Vorgänge in einer virtuellen Maschine verfolgt werden können. Primärer Einsatzzweck dieser Technologie ist
die Erstellung von Laufzeitprofilen, in denen Zeit- und Speicherverbrauch gemessen werden. Der Zugang zur virtuellen Maschine erfolgt
über die native Schnittstelle, so dass bei Einsatz der JVMPI Laufzeitbibliotheken in C/C++ zu erstellen sind.
JPDA: Java Platform Debugging Architecture ist eine Architektur, die für
die Entwicklung von Debugger-Werkzeugen für die virtuelle Maschine vorgesehen ist. Innerhalb dieser Architektur gibt es mit dem Java
Debug Interface (JDI) eine reine Java Schnittstelle, mit der eine technische Verbindung zu einer weiteren virtuellen Maschine hergestellt
werden kann.
• Für die Entwicklung des Prototypen werden verschiedene freie Java-Bibliotheken von Drittherstellern eingesetzt. Hierzu zählen:
– Novosoft Metadata Framework und Novosoft UML1 4 library für den
Export der statischen und dynamischen Struktur der Zielapplikation in
XMI-Dateien [URL-Novosoft].
– Log4J. Ein Logging-Framework für die Programmiersprache Java [URLLog4J].
– The Byte Code Engineering Libary (bcel). Eine Java-Bibliothek für
die Manipulation von Java-Bytecode Dateien [URL-Bcel].
KAPITEL 5. PROTOTYP
30
– Xerces. Frei verfügbare Implementation eines XML-Parsers [URLXerces].
• Für den Unittest der Implementation wird das Framework JUnit verwendet
[URL-JUnit].
• Zur Visualisierung der erzeugten XMI-Dateien wird die freie Version des
CASE-Tools Poseidon for UML Community Edition 1.50 eingesetzt [URLPoseidon].
• Die Erstellung der grafischen Benutzungsoberfläche erfolgt in Java Swing.
5.2
Architektur
“Software-Architektur bildet den schwierigen Übergang von der Analysephase zur konkreten technischen Realisierung. Dabei schlägt sie
die Brücke [...], indem sie die geforderte Funktionalität auf eine Struktur von Softwarekomponenten und deren Beziehungen untereinander
abbildet.” (Starke 2000, S. 22)
Eine Software-Architektur beschreibt die wesentlichen Merkmale und Eigenschaften der einzelnen Systemkomponenten. Die Architekturbeschreibung erleichtert das Verständnis komplexer Strukturen, weil sie in der Sichtweise auf das System von konkreten Details abstrahiert.
In den folgenden drei Unterabschnitten wird die Architektur des Prototypen
gezeigt. Das Programm wird als reaktives System basierend auf einer 3-Schichtenarchitektur entwickelt. Zu den externen Akteuren zählt neben dem Benutzer auch
das zu beobachtende Zielsystem in seiner Laufzeitumgebung.
Zur Verdeutlichung der Systemgrenzen wird zunächst die Struktur des Gesamtsystems beschrieben, in das der Prototyp eingebunden ist. Danach werden im
Systementwurf die einzelnen Pakete der Anwendung und deren Zuständigkeiten
beschrieben. In einem Abschnitt zum Objektdesign wird dann die Klassenstruktur
ausgewählter Pakete näher beleuchtet.
Abgesehen von der Analysephase, in der der Prototyp die statische Struktur
des Zielsystems analysiert, findet der Informationsaustausch mit dem entfernten System nicht direkt, sondern indirekt über ein weiteres System, der bereits
erwähnten JPDA, statt.
KAPITEL 5. PROTOTYP
31
Abbildung 5.4: Systemarchitektur
Die Abbildung 5.4 zeigt zwei virtuelle Maschinen M1 und M2, die als eigenständige Prozesse mit jeweils eigenem Adressraum laufen. Der Prototyp wird
von M1 ausgeführt, während das Zielsystem von M2 ausgeführt wird. M2 wird
durch entsprechende Benutzereingaben über M1 gestartet. Danach findet der Informationsfluss zwischen Prototyp und Zielsystem ausschließlich über die Mechanismen der JDPA statt.
Die Mirror Komponente spiegelt die Vorgänge auf der entfernten Maschine.
Die dort stattfindenden Vorgänge werden durch zugehörige Ereignisobjekte gemeldet. Die Ereignisse werden vom Prototypen aus einer Queue ausgelesen und
bei Bedarf weiterverarbeitet. Der Mirror fungiert gleichzeitig als Filter. Es werden
nur solche Ereignisse in die Queue geschrieben, für die zuvor explizit ein Request
aktiviert wurde. Durch diese Filterung wird die Vielzahl von Ereignismeldungen
auf die relevanten Ereignise reduziert.
5.2.1
Systementwurf
In diesem Abschnitt wird die Paketstruktur des Prototypen gezeigt. Abbildung 5.5
zeigt die Pakete und deren Zuordnung zu den Schichten “Präsentation/Interaktion”,
“Funktion” und “Fachdomäne”. Im Folgenden werden die Zuständigkeiten der
einzelnen Pakete näher beschrieben.
Repository: Das Repository verwaltet jeweils eine interne Repräsentation der
statischen und dynamischen Struktur des Zielsystems. Mit diesem Paket
KAPITEL 5. PROTOTYP
32
Abbildung 5.5: Paketstruktur
werden die fachlichen Objekte realisiert. Ein konzeptionelles Modell der
statischen und dynamischen Struktur einer Zielapplikation zeigt die Abbildung A.1 im Anhang. Für die Speicherung der Daten wird das vom Betriebssystem zur Verfügung gestellte Dateisystem benutzt.
Classresolver: Die Strukturenanalyse des Zielsystems erfolgt mit den Reflexionsmechanismen, die die Programmiersprache Java bietet. Die Klassen der
Zielanwendung liegen physisch als binäre Dateien in einer Verzeichnisstruktur des Dateisystems. Der Classresolver identifiziert die Menge der zu
analysierenden Klassen und instanziiert die zugehörigen Klassenobjekte.
Diese Klassenobjekte werden für die Analyse der statischen Struktur benötigt.
Tracer: Die Ausführung und Beobachtung der Zielanwendung erfolgt durch den
Tracer. Die Klassen in diesem Paket realisieren eine Umgebung, in der das
Zielsystem gestartet, kontrolliert ausgeführt und beobachtet werden kann.
Die Klassen benutzen die Schnittstellen zur JPDA. Sie realisieren die Funktionalität, um die Informationen über die Vorgänge im Zielsystem zu sammeln.
Patcher: Der Patcher ist ein Werkzeug zur vollautomatischen Manipulation der
binären Javaklassen. Die Manipulation wird erforderlich, wenn die konkreten Rückgabewerte der Methoden ausgewertet werden sollen. Normalerweise stehen diese nicht explizit zur Verfügung. Durch geeignete Manipulation der Methoden können deren Rückgabewerte jedoch zur Ausführungszeit
KAPITEL 5. PROTOTYP
33
sichtbar gemacht werden.
Controller: Der Controller ist als zentrale Komponente für die Steuerung und
Kontrolle der internen Programmabläufe zuständig. Die externen Benutzereingaben werden entgegen genommen und entsprechende Funktionen
ausgelöst. Ein grosser Teil der Funktionalität wird an die zuständigen Komponenten delegiert. Der Controller ist gleichzeitig dafür zuständig, die GUI
mit Informationen zu versorgen.
Gui: Die Bedienung des Prototypen erfolgt durch eine Java Swing basierte grafische Benutzungsoberfläche. Die GUI nimmt die Benutzereingaben entgegen und präsentiert die programminternen Zustände und Abläufe.
5.2.2
Objektentwurf
In diesem Unterabschnitt werden die Klassen und Schnittstellen beschrieben mit
denen die in Abschnitt 5.1.2 aufgezählten Anforderungen realisiert werden. In
den folgenden Beschreibungen wird auf die Pakete controller, repository
und tracer näher eingegangen. Sie realisieren die wesentliche Systemfunktionalität. Auf eine Darstellung der Pakete gui, classresolver und patcher wird
verzichtet.
Controller
Mit diesem Paket wird das bereits in der Einleitung (siehe Abschnitt 1) erwähnte
Entwurfskonzept der “Trennung von Funktion und Interaktion” umgesetzt. Das
Ziel dabei ist die Entkopplung des interaktiven Teils der Anwendung vom funktionalen Teil. Der Controller realisiert den funktionalen, die Klassen im Paket gui
realisieren den interaktiven Teil. Dadurch können die beiden Bereiche unabhängig
voneinander entwickelt werden. (vgl. Züllighoven 1998, S. 234 ff.)
Die lose Kopplung der Teile wird durch das Beobachtermuster realisiert. Der
Controller (das beobachtete Subjekt) bietet mit seiner Schnittstelle die Funktionalität nach außen an und trifft keine konkreten Annahmen über die Dienstnutzer
(die Beobachter). Diese haben sich beim Controller registriert, so dass dieser beim
Eintreten bestimmter Ereignisse alle seine Beobachter informieren kann. Im weiteren Verlauf erfragen die Beobachter die für sie relevanten Informationen beim
Controller, um sich mit der neuen Situation zu synchronisieren. (vgl. Gamma u. a.
1996, S. 257 ff.)
KAPITEL 5. PROTOTYP
34
Wie bereits im vorigen Abschnitt erwähnt, delegiert der Controller bestimmte
Anfragen an dafür zuständige Komponenten, die nach außen gar nicht in Erscheinung treten. Dadurch kann sich die Interaktionskomponente auf die Nutzung einer
einzigen Schnittstelle beschränken.
Repository
Die Analyse der statischen Struktur der Zielapplikation erfolgt durch Untersuchung der binären Programmdateien (Java-Bytecode). In einer nicht festgelegten
Reihenfolge werden zunächst nacheinander alle Klassen des Zielsystems identifiziert und zu einer Menge zusamengefasst, bevor sie an das Repository übergeben
werden.
Das Repository analysiert die Klassen und bildet damit die statische Struktur des Zielsystems mit einer internen Repräsentation nach. Zur Erkennung der in
Anforderungsgruppe I geforderten Artefakte und Eigenschaften wird der Reflexionsmechanismus der Programmiersprache Java benutzt. Die nachfolgend aufgelisteten Abbildungen werden dadurch ermöglicht:
• Pakete 7→ java.lang.Package
• Klassen 7→ java.lang.Class
• Interfaces 7→ java.lang.Class
• Konstruktoren 7→ java.lang.reflect.Constructor
• Methoden 7→ java.lang.reflect.Method
Systä identifiziert in ihrem Ansatz weitere Artefakt, wie Instanzvariablen und
statische Initialisierungsblöcke. Da die oben aufgezählten Artefakte jedoch ausreichen, um das Systemverhalten hinreichend genau zu beobachten, kann in einer
ersten Version auf die Identifikation der Felder und Blöcke verzichtet werden.
(vgl. Systä 2000b, S. 121 ff.)
Mit den Objekten des Reflexionsmechanismus können die verschiedenen Eigenschaften der Artefakte leicht erkannt werden. So haben Methoden immer einen
Rückgabetyp und eine ggf. leere Liste von Typen der Übergabeparameter. Auch
die Sichtbarkeit der Methoden (public, protected, private) kann erkannt werden.
Da sich diese Diplomarbeit auf die Analyse der dynamischen Struktur konzentriert, werden die statischen Beziehungen der Artefakte nur soweit wie erforderlich ermittelt. Erkannt wird die hierarchische Struktur der Elemente: ein Paket
35
KAPITEL 5. PROTOTYP
Abbildung 5.6: Repository
enthält Klassen und Klassen enthalten Methoden. Weiterhin kann die Vererbungsbeziehung leicht erkannt werden. Dagegen werden horizontale Beziehungen (Aggregation und Assoziation) nicht ermittelt.
Abbildung 5.6 zeigt die Klassenstruktur des Paketes, das die statische und dynamische Struktur des Zielsystems verwaltet. Dazu zählt auch der Export und das
Speichern in das Dateisystem. Eine direkte softwaretechnische Umsetzung des
fachlichen Modells (siehe Abbildung A.1) ist aufwendig und nicht erforderlich,
wenn die reflexiven Eigenschaften der Programmiersprache bzw. die Objekte in
dem Paket java.lang.reflect benutzt werden.
Die Verwaltung der statischen Struktur des Zielsystems wird in der Klasse
StaticStructure gekapselt. Die hierarchischen Strukturen der Artefakte wird
mit Containerklassen vom Typ Dictionary nachgebildet. Mit einem Dictionary
werden jeweils Pakete als Schlüsselelemente auf die Menge der in diesem Paket enthaltenen Klassen abgebildet. Ein weiteres Dictionary bildet eine Klasse als
Schlüsselelement auf die Menge der von ihr implementierten Methoden ab.
Für die Verwaltung hierarchischer Strukturen ist im Allgemeinen das Kompositionsmuster gut geeignet (vgl. Gamma u. a. 1996, S. 213 ff.). Neben der
Strukturierung der Elemente sind hierbei zusätzlich Such- bzw. Traversierungsalgorithmen zu implementieren, um die Objekte in der hierarchischen Struktur
KAPITEL 5. PROTOTYP
36
aufzufinden. Der hier gewählte Ansatz erfüllt jedoch die erforderliche Funktionalität, ist einfach und bietet indizierten Zugriff auf die Elemente, ohne zusätzlich
Such- bzw. Traversierungsalgorithmen zu implementieren.
Die Klasse DynamicStructure kapselt eine Abbildung der Vorgänge auf dem
Zielsystem. Hierzu stellt sie eine Schnittstelle zur Verfügung, mit der Botschaftenobjekte erzeugt und in der Reihenfolge ihres Auftretens verwaltet werden. Die
für die Generierung der Botschaftenobjekte erforderlichen Informationen werden
an der Schnittstelle beim Aufruf mit übergeben.
Zu jeder Botschaft gehören immer genau ein Sender und ein Empfänger. Die
agierenden Objekte auf dem Zielsystem werden mit Instanzen der Klasse
ConcreteInstance repräsentiert. Zusätzlich wird jedem Botschaftenobjekt die
entsprechende Methode zugeordnet, die von ihm ausgelöst wird.
Der Entwurf dieser Struktur basiert auf dem UML-Interchange Metamodel
(vgl. OMG 2001, S. 5-12). Dort werden die Botschaften als Actions bezeichnet.
Die Orientierung am UML Standard ermöglicht den Export des Botschaftenflusses als UML-Sequenzdiagramm. Hierfür wird die Sequenz der Botschaften an
eine Instanz der Klasse UMLBehaviourModeler (nicht abgebildet) übergeben, die
die Objekte in eine XML Datei speichert, mit der ein entsprechendes Sequenzdiagramm beschrieben wird.
Für die Erzeugung einer Metrik, mit der die Methodenaufrufe gezählt werden,
ist die Klasse SimpleProfiler (nicht abgebildet) zuständig. Durch Auswertung
der Botschaftensequenz können die entsprechenden Informationen generiert werden.
Tracer
Dieses Paket übernimmt eine der zentralen Aufgaben des Prototypen: Das Zielsystem wird in einer kontrollierbaren Umgebung gestartet und beobachtet. Die damit verbundenen Einzelaufgaben ergeben einen großen Aufgabenkomplex. Zwar
sind die einzelnen Klassen jeweils für einen abgegrenzten Bereich verantwortlich,
dennoch sind die gegenseitigen Abhängigkeiten der einzelnen Module nicht so
ohne weiteres überschaubar. Zusätzliche Komplexität entsteht durch die technische Notwendigkeit, nebenläufige Prozesse zu verwalten. Abbildung 5.7 zeigt die
Klassenstruktur.
Um die Komplexität nach außen hin zu reduzieren, wurde die Klasse
TraceController eingeführt. Sie ist eine Ausprägung des Entwurfsmusters Me-
37
KAPITEL 5. PROTOTYP
Abbildung 5.7: Tracer
KAPITEL 5. PROTOTYP
38
diator, mit dem das Zusammenspiel einer Menge von Objekten gekapselt wird.
Um die Komplexität einer Komponente nach außen zu reduzieren, würde auch
eine Fassade genügen. Eine Fassade bietet genau eine abstrakte Schnittstelle. Sie
implementiert jedoch im Gegensatz zum Mediator keine weitere Funktionalität
und die von ihr verborgenen Objekte kennen die Fassade nicht (vgl. Gamma u. a.
1996, S. 189 ff.). In diesem Entwurf ist es allerdings erforderlich, dass der TraceController nicht nur die Schnittstelle nach außen, sondern auch Kontrollfunktionalität übernimmt. Aus diesem Grund ist ein Mediator besser geeignet.
Zu den konkreten Aufgaben des TraceControllers zählt das Starten und Stoppen des Zielystems. Für den Start erzeugt er eine Instanz vom Typ TracerThread.
Weil bei der Erzeugung gleichzeitig die entfernte virtuelle Maschine eingerichtet und gestartet wird, muss der Name der auszuführenden Startklasse angegeben
werden. Die Verwaltung der entfernten virtuellen Maschine wird durch die Oberklasse LauncherThread realisiert.
Daneben verwaltet der TraceController eine Instanz vom Typ ComponentFilter, der die zu beobachtenden Komponenten enthält. Beim ComponentFilter
können zur Laufzeit des Zielsystems Komponenten an- bzw. abgemeldet werden.
Unmittelbar nach dem Start des Zielsystems wird der Prozess der virtuellen
Maschine solange suspendiert, bis der EventQueueReader eingerichtet und gestartet wurde. Die während der Ausführung des Zielsystems auftretenden Programmereignisse werden von der entfernten virtuellen Maschine dem lokalen Mirror
übergeben. Von dort werden sie durch den EventQueueReader ausgelesen.
Ein Javaprogramm, das auf einer virtuellen Maschine ausgeführt wird, die das
JVMDI2 implementiert, meldet bei Bedarf eine Reihe verschiedener Ereignisse.
Hierzu zählen u.a. das Starten und Enden eines Threads, das Vorbereiten, Laden,
und Entladen einer Klasse, der Aufruf bzw. das Beenden einer Methode und weitere.
Für die Verfolgung des Programmablaufes ist es ausreichend, sich auf die Meldungen zu konzentrieren, mit denen jeweils das Betreten und Verlassen einer Methode angezeigt wird. Aufrufe von Konstruktoren (Objekterzeugung) werden als
Methodenaufrufe mitgeteilt. Das Ereignis Methodentry wird zu Beginn einer Methode gemeldet, unmittelbar vor Ausführung der ersten Instruktion, das Ereignis
Methodexit wird zum Ende der Methode gemeldet, unmittelbar vor dem Rück2 Java
Virtualmachine Debug Interface. Eine Schnittstelle, die im Rahmen einer Referenzarchitektur der Firma Sun entwickelt wird.
39
KAPITEL 5. PROTOTYP
Aufrufkontext
Methodenname
Argumente (Wert und Typ)
Empfängerobjekt (Wert und ID)
Rückgabetyp
(1)
X
X
(2)
X
X
X
(3)
X
X
X
X
Tabelle 5.1: Verfügbare Informationen der Methodenereignisse
sprung zum Aufrufer.
Beim Starten des Zielsystems wird zusätzlich eine Instanz der Klasse RequestManager
erzeugt und durch den TracerThread verwaltet. In die EventQueue werden nur
solche Ereignisse geschrieben, für die der RequestManager zuvor ein Request
bei dem Mirror anmeldet. Für die Programmverfolgung sind dies lediglich die
Requests für die Ereignisse, mit denen das Betreten und Verlassen einer Methode gemeldet wird. Zusätzlich können die Requests mit Klassennamen verknüpft
werden. Dadurch werden nur solche Methodenaufrufe gemeldet, die an Instanzen
dieser Klasse gerichtet werden. Änderungen im ComponentFilter werden an den
RequestManager weitergeleitet. Dadurch kann die Auswahl der zu beobachtenden
Komponenten zur Laufzeit geändert werden.
Die gemeldeten Ereignisobjekte liefern die erforderlichen Informationen, um
die Systemvorgänge ausführlich mitzuprotokollieren. Je nach Signatur der Methode ist der Aufrufkontext zu unterscheiden in: (1) Konstruktor (2) Klassenmethode (3) Instanzmethode. Die Tabelle 5.1 zeigt die verfügbaren Informationen
in Abhängigkeit des jeweiligen Kontextes (X bedeutet verfügbar). Der konkrete
Rückgabewert statischer und nichtstatischer Methoden ist nicht mit aufgeführt.
Dieser wird nicht explizit von den Ereignisobjekten zur Verfügung gestellt. Dennoch kann er ermittelt werden. Eine Erläuterung hierzu erfolgt in Abschnitt 5.3.
Der EventQueueReader verwaltet für jeden aktiven Thread auf der entfernten
virtuellen Maschine eine Instanz vom Typ TraceEventHandler. Beim Auslesen
der EventQueue werden die einzelnen Ereignisse den entsprechenden TraceEventHandlern zur Weiterverarbeitung übergeben. Die Ereignisfilterung auf Klassenebene erfolgt, wie bereits erwähnt, durch die Requests. Auf Methodenebene muss
ein eigener Filtermechanismus realisiert werden. Hierfür arbeiten die TraceEventHandler mit dem ComponentFilter zusammen. Ein TraceEventHandler extrahiert
die Informationen aus den Ereignisobjekten und liefert die Resultate an den TraceConroller, der diese Informationen dann seinen Klienten zur Verfügung stellt.
KAPITEL 5. PROTOTYP
40
Mit diesem Mechanismus werden die Vorgänge in dem Zielsystem durch den Prototypen erfasst und für die weitere Verarbeitung aufbereitet.
5.3
Implementierung
Die Implementierung des Entwurfs erfolgt unter der Entwicklungsumgebung Eclipse unter dem Betriebssystem Linux. Aufgrund der Plattformunabhängigkeit der
Programmiersprache Java ist davon auszugehen, dass eine Portierung auf andere
Plattformen, z.B. Windows, relativ leicht bewerkstelligt werden kann. Die Entwicklungsplattform Eclipse steht sowohl für Linux/Unix als auch für Windows
zur Verfügung (siehe [URL-Eclipse]).
Die in Abschnitt 5.1.2 aufgeführten Anforderungen wurden teilweise mit prototypischen Labormustern ermittelt und verifiziert. Dabei zeigte sich bei dem Einsatz der Technologien JPDA und JVMPI, dass mit JPDA eine geeignete Architektur zur Verfügung steht. Im Gegensatz dazu ist es beispielsweise unter JVMPI
nicht gelungen, beim Aufruf einer Methode die aktuellen Werte der Übergabeparameter zu ermitteln. Deshalb wurde im weiteren Entwicklungsverlauf auf den
Einsatz von JVMPI verzichtet.
Im Zuge der Entwicklung wurde festgestellt, dass die Ermittlung der konkreten Rückgabewerte der Methoden problematisch ist. Die Mechanismen der JPDA
bieten hierbei keinerlei Unterstützung. Dennoch können die Rückgabewerte ermittelt werden, wobei der Ansatz auf die Manipulation des Java-Bytecodes basiert. Diese Manipulation kann prinzipiell vollständig automatisiert und für den
Benutzer transparent erfolgen.
Für die Manipulation ist in dem Paket patcher eine Klasse Patcher vorgesehen, mit der entsprechende Manipulationen an den Methoden vorgenommen
werden können. Die Idee dabei ist, den Rückgabewert der Methode unmittelbar
vor dem Rücksprung zum Aufrufer vom Operandenstack zu kopieren und einer
lokalen Variablen zuzuweisen. Diese Variable wird der Methode als lokale Variable hinzugefügt. Sie wird nach dem Methodennamen mit angestelltem $ benannt.
Mit den Mechanismen der JPDA kann dann, beim Eintritt des Ereignisses MethodExit, der aktuelle Rückgabewert aus der lokalen Variablen ausgelesen werden.
Für einfache Beispiele kann die Machbarkeit des Ansatzes gezeigt werden.
Die Entwicklung der Klassen des Prototyps fand unter Einsatz des Rahmenwerkes JUnit und dem Testfirst-Ansatz statt. Bei dieser testgetriebenen Entwick-
KAPITEL 5. PROTOTYP
41
lung musste jedoch aus Zeitgründen auf die Messung der Testabdeckung verzichtet werden. Im Rahmen einer evolutionären Weiterentwicklung des Prototypen
wären die Testfälle zu überarbeiten.
Kapitel 6
Fallbeispiel
In diesem Kapitel wird die Arbeitsweise des realisierten Prototypen anhand eines praktischen Anwendungsbeispiels gezeigt. Als Zielanwendung wird das Testwerkzeug JUnit untersucht. JUnit ist ein Framework für die Programmiersprache
Java, das den Entwickler bei der Erstellung und Ausführung von Unittests unterstützt. Insbesondere können einmal erstellte Testfälle wiederholt durchlaufen
und zu komplexen Testsuiten zusammengefasst werden. Für nähere Einzelheiten
sei auf die Homepage des Frameworks verwiesen [JUnit].
Um das Anwendungsbeispiel übersichtlich zu halten, wird eine Testklasse
PersonTest entwickelt. Mit ihr werden Objekte einer einfachen Klasse Person
getestet. Die Klasse Person kapselt einen Namen und ein Geburtsdatum. Getestet
wird die Objekterzeugung und -initialisierung. Der Quellcode der beiden Klassen
ist dem Anhang beigefügt (siehe Anhang B).
Abbildung 6.1 auf Seite 43 zeigt den Prototypen, nachdem er gestartet und
JUnit geladen wurde. Während des Ladevorgangs wird die Klassenstruktur analysiert. Als Ergebnis zeigt der Komponentenbrowser die in JUnit enthalten Pakete,
Klassen und Methoden.
Der Browser dient zur Orientierung in der Komponentenstruktur. Für Klassen werden deren Oberklassen, für Methoden werden deren Signaturen angezeigt.
Gleichzeitig dient der Browser zur Markierung der Komponenten, deren Verhalten bei der späteren Programmausführung beobachtet werden soll. Damit das Zielprogramm durch den Prototypen gestartet werden kann, muss die Startklasse angegeben werden. In diesem Fall ist das die Testklasse PersonTest, die in ihrer
main-Methode den TestRunner von JUnit startet.
Die Ausführung der Startklasse und die Beobachtung des Zielsystems erfolgen
42
KAPITEL 6. FALLBEISPIEL
Abbildung 6.1: Komponentenbrowser
Abbildung 6.2: Aktionsmonitor
43
KAPITEL 6. FALLBEISPIEL
44
mit dem Aktionsmonitor. Er dient auch zur Kontrolle der Programmverfolgung.
Abbildung 6.2 auf Seite 43 zeigt den Aktionsmonitor, nachdem JUnit gestartet
wurde. Das Bild zeigt im Vordergrund den Prototypen, JUnit wird im Hintergund
ausgeführt.
Die Vorgänge in JUnit erfolgen weitgehend unabhängig von der Beobachtung
durch den Prototypen. Nach dem Start erscheint die Benutzungsoberfläche wie
üblich auf dem Bildschirm und kann bedient werden. Die dadurch ausgelösten
Vorgänge werden zur Ausführungszeit vom Prototypen erfasst und, wie in der
Abbildung zu erkennen, im Aktionsmonitor angezeigt.
Der Systemtrace kann bei Bedarf in einer Textdatei gespeichert werden. Im
Anhang befindet sich der vollständige Systemtrace des Fallbeispiels (siehe Anhang B.3). Er enthält ausführliche Informationen der Vorgänge auf der entfernten
Maschine. Mit den vertikalen Linien wird der Kontrollfluss bzw. der Aufrufstack
nachgezeichnet. Mit spitzen Klammern werden die Ereignisse MethodEntry und
MethodExit angezeigt. Weiterhin werden zu Beginn einer Methode die aktuellen
Werte der Übergabeargumente, der Empfänger und sein aktueller Zustand angezeigt. Beim Verlassen der Methode wird der Rückgabetyp angezeigt und auch,
wenn er vorliegt, der Rückgabewert sowie der Objektzustand.
Durch Erfassung des Objektzustandes am Anfang und am Ende einer Methode
können Zustandsänderungen der Objekte verfolgt werden. Darauf wird im Rahmen der vorliegenden Diplomarbeit nicht näher eingegangen.
Je nach Anzahl der beobachteten Komponenten fällt der Umfang des Systemtraces unterschiedlich groß aus. Im Fallbeispiel werden die Klassen PersonTest
und alle ihre Oberklassen (junit.framework.TestCase und junit.framework.Assert)
zur Beobachtung markiert. Auch das Verhalten der Testobjekte, also Instanzen der
Klasse Person, werden beobachtet. Bei Ausführung des Testfalls werden in dieser Konfiguration insgesamt 63 Botschaften gezählt. Die jeweilige Rückkehr zum
Aufrufer wird dabei als Aktion mitgezählt.
Bei umfangreicheren Interaktionsfolgen ist der Systemtrace aufgrund seiner
Größe nicht mehr manuell auswertbar. Aus diesem Grund wird die Botschaftensequenz in einer XML Datei gespeichert, die eine Modellbeschreibung auf Basis
von XMI enthält. Ein solches Modell kann mit geeigneten CASE-Tools visualisiert werden. Eine entsprechende Modellbeschreibung für das durchgeführte Fallbeispiel ist im Anhang unter B.5 zu finden.
Es kann gezeigt werden, dass das CASE-Tool Poseidon diese Datei importiert
KAPITEL 6. FALLBEISPIEL
45
und das Modell zwar vollständig erkennt, dieses jedoch nur teilweise visualisieren kann. Der Grund hierfür ist, dass neben den semantischen Modellinformationen zusätzliche Informationen für die Darstellung der einzelnen Modellelemente
benötigt werden. Diese sind derzeit allerdings nicht standardisiert (vgl. hierzu Abschnitt 4.3). Das Tool erwartet spezifische Visualisierungsinformationen für das
Modell in einer separaten Datei. Die Erzeugung einer entsprechenden Datei kann
im Rahmen der Diplomarbeit nicht geleistet werden.
Auf Basis des erstellten Systemtraces kann, neben der Visualisierung mit einem Sequenzdiagramm, eine einfache Messung der Aktivität erfolgen. Den einzelnen Funktionen wird die Zahl ihrer Aufrufe zugeordnet. Dadurch können z.B.
nicht aufgerufene Methoden identifiziert werden. Besonders häufig aufgerufene
Methoden können gleichzeitig als Kandidaten für Optimierungsüberlegungen herangezogen werden. Eine Metrik für die Klassen des Fallbeispiel ist im Anhang
unter B.4 zu finden. Für die Klasse Person zeigt sie z.B., dass die Zugriffsmethoden für den Geburtstag nicht ausgeführt und damit auch nicht getestet wurden.
6.1
Bewertung
Das Hauptziel der vorliegenden Diplomarbeit ist die Rekonstruktion der Dynamik
eines objektorientierten Programms. Mit dem entwickelten und hier vorgestellten
Prototypen konnte das Hauptziel erreicht werden. Insbesondere werden mit dem
Prototypen folgende Punkte gezeigt:
• Es ist möglich, den Botschaftenfluss eines objektorientierten Programms
ausführlich aufzuzeichnen, ohne dabei den Quellcode zu manipulieren bzw.
zu kennen. Die Beobachtung basiert auf den binären Dateien des Zielsystems. Vorteilhaft aber nicht notwendig ist die Berücksichtigung von Debuggingsymbolen bei der Übersetzung der Quelldateien.
• Aus den aufgezeichneten Informationen kann der Botschaftenfluss vollständig
rekonstruiert werden.
• Auf Basis der erfassten Vorgänge im Zielsystem kann eine Abdeckung gemessen werden, die Aussagen darüber zulässt, welche Methoden wie oft
bzw. gar nicht aufgerufen wurden.
Kapitel 7
Schluss
Die Idee, durch Verfolgung des Botschaftenflusses die dynamische Struktur objektorientierter Programme darzustellen, wird bereits seit Ende der achtziger Jahre
verfolgt (vgl. Kleyn und Gingrich 1988). Im Folgenden werden zwei ausgewählte
Ansätze dargestellt. Abschließend erfolgen die Zusammenfassung der Diplomarbeit und der Ausblick.
Programmverfolgung in Smalltalk
Böcker und Herczeg entwickeln entsprechende Ansätze, um die dynamischen Eigenschaften von Smalltalk Programmen zu ermitteln (vgl. Böcker und Herczeg
1990a, b). Dabei unterscheiden Sie zwischen der Verfolgung von Methoden (method trace) und der Verfolgung von Nachrichten (message trace). Der Unterschied
besteht in dem jeweiligen Abstraktionsniveau und entspricht den Konzepten objektorientierter Programmiersprachen. Die Verfolgung der Methoden ist ein Vorgang auf geringerer Abstraktionsebene als die Verfolgung der Nachrichten.
Der konkrete Programmablauf bzw. die Aktivität der Objekte wird durch Beobachtung der ausgeführten Methoden verfolgt. Die Nachrichtenverfolgung bezieht sich dagegen auf die Interaktionen der einzelnen Objekte, so dass der Kommunikationsprozess der beteiligten Objekte beobachtet wird.
Die Auswahl der zu beobachtenden Objekte und Methoden erfolgt visuell am
Bildschirm. Mit einem geeigneten Werkzeug (graphical tracer) wird ein Objektmodell konstruiert, das die Zielanwendung bzw. einen Ausschnitt daraus darstellt.
Das Modell beschreibt die konkreten Instanzen und deren Beziehungen. Es dient
gleichzeitig zur Markierung der zu verfolgenden Abläufe. Die Idee der Autoren
ist die Kombination visueller Programmierung mit Programmvisualisierung, wo46
KAPITEL 7. SCHLUSS
47
bei letztere durch ein Textprotokoll am Bildschirm erfolgt.
Ein Vorteil des Ansatzes ist die Feingranularität, mit der die Verfolgung bestimmt werden kann. Es werden nicht automatisch alle Instanzen einer Klasse
beobachtet, die Verfolgung kann auf bestimmte Instanzen und auf bestimmte Methoden beschränkt werden.
Der Systemtrace wird, wie bereits erwähnt, als Textprotokoll angezeigt. Die
Auswertung erfolgt manuell durch den Benutzer, hierfür sind keine weiteren Werkzeug vorgesehen.
Shimba
Systä entwickelt mit Shimba eine Umgebung, in der Java-Programme in einem
Reverse-Engineering Prozess untersucht werden. Shimba ist vollständig von der
jeweiligen Entwickungsumgebung entkoppelt. Bei der Untersuchung wird nur der
binäre Java-Bytecode berücksichtigt. Dadurch kann die Abhängigkeit von der Zielapplikation deutlich verringert werden. (vgl. Systä 2000a, b)
In Shimba werden zwei externe Reengineering-Werkzeuge integriert, die hier
nur kurz angerissen werden. Für detaillierte Informationen sei auf die entsprechenden Webseiten verwiesen (siehe [URL-Rigi], [URL-SCED]):
Rigi: Rigi ist ein Werkzeug, das die Artefakte eines Softwaresystems und deren
Beziehungen mit Graphen visualisiert. Neben der reinen Darstellung der
statischen Struktur bietet es die Möglichkeit, voneinander abhängige Teilbereiche per Anfrage zu identifizieren und herauszufiltern.
SCED: Ein Werkzeug zur Darstellung und Modellierung dynamischer Abläufe.
Die Anwendung bietet einen Editor zur Bearbeitung von Szenario- und Zustandsdiagrammen, wobei es sich hier um Variationen der entsprechende
Diagramme aus der UML handelt.
Der primäre Einsatzzweck der Umgebung ist die Ermittlung und Darstellung
der dynamischen und statischen Struktur des Java-Programmes. Für die statische
Analyse wird der Bytecode der Zielanwendung analysiert. Die bei der Analyse ermittelten Artefakte und deren Beziehungen werden mit dem Werkzeug Rigi
visuell dargestellt, so dass die zu beobachtenden Programmbereiche ausgewählt
werden können. Durch Ausführung in einem speziell angepassten Debugger werden Informationen über den Programmablauf gewonnen, die mit SCED visuali-
KAPITEL 7. SCHLUSS
48
siert werden können.
Shimba erlaubt eine detaillierte Verfolgung der Programmvorgänge. Die Ablaufverfolgung ist nicht auf Methodenebene beschränkt. Bei der Analyse des JavaBytecodes werden auch die Kontrollstrukturen in den Methoden berücksichtigt.
Bei der Visualisierung werden die Ablaufpfade dargestellt.
7.1
Zusammenfassung
Die Aufgabe der vorliegenden Diplomarbeit ist die Rekonstruktion der Dynamik
objektorientierter Systeme. Aus unterschiedlichen und dargestellten Gründen gibt
es hierfür Werkzeugbedarf. Vorhandene Konzepte und Technologien wie Logging
und Debugging sind nützlich, reichen aber unter Umständen nicht aus, um ein
komplexes objektorientiertes System zur Ausführungszeit geeignet zu verfolgen.
Der in dieser Arbeit verfolgte Ansatz zur Programmbeobachtung basiert auf
dem Ablesen der Systemdynamik am Botschaftenfluss der Objekte. Die dafür erforderlichen Informationen werden technisch aus der Laufzeitumgebung des Zielsystems gewonnen. Auch der Quellcode wird nicht berücksichtigt. So kann die
Unabhängigkeit vom Zielsystem weitgehend gewährleistet werden.
Als Ergebnis dieser Arbeit ist eine prototypische Realisierung entstanden, mit
der die Machbarkeit des Ansatzes gezeigt wird. Daneben kann der entstandene
Prototyp als Grundlage für eine evolutionäre Weiterentwicklung herangezogen
werden.
Die aus der Ablaufverfolgung gewonnenen Informationen werden genutzt,
um die dynamische Struktur des Zielsystems zu modellieren. Die Beschreibung
und Darstellung der Modelle basieren auf offenen und allgemeinen Standards wie
UML und XMI. Dadurch wird die Abhängigkeit zu propritären Lösungen von
Drittanbietern auf ein Minimum reduziert. Die weiter oben vorgestellte ReverseEngineering Umgebung Shimba weist trotz ihrer Leistungsfähigkeit diesen Nachteil auf. Mit den externen Komponenten Rigi und SCED werden leistungsfähige
und zweckmäßige Komponenten integriert, die sich allerdings nur zum Teil an die
Standards halten.
Ein weiteres Problem wird in der Heterogenität der Werkzeuglandschaft von
Shimba gesehen. Dem Benutzer wird die Einarbeitung und die Handhabung unterschiedlicher Anwendungen zugemutet.
KAPITEL 7. SCHLUSS
49
Ausblick
Der entwickelte Prototyp ist in seinem derzeitigen Stadium eher nicht als Testwerkzeug zu bezeichnen. Zwar gelingt es, einen bestimmten Programmablauf als
Sequenzdiagramm in ein CASE-Tool zu importieren, doch wird damit lediglich
die Voraussetzung für die Testdurchführung geschaffen. Der eigentliche Soll-Ist
Vergleich steht zu diesem Zeitpunkt noch aus. Unter realen Projektbedingungen
wäre der manuelle Vergleich einer großen Anzahl von Interaktionsfolgen wirtschaftlich nicht vertretbar.
Vor diesem Hintergrund könnte z.B ein nächster Entwicklungsschritt darin
bestehen, die Testbarkeit der erzeugten Modellbeschreibungen zu gewährleisten
und eine entsprechende Testumgebung zu schaffen, mit der die Tests automatisch
durchgeführt werden können.
Im Rahmen einer Weiterentwicklung wäre auch zu untersuchen, ob auf Ebene des Botschaftenflusses ausreichend getestet werden kann oder ob es erforderlich ist, den Kontrollfluss innerhalb der Methoden zu berücksichtigen. Vermutlich gibt es hier unterschiedliche Anforderungen für die verschiedenen Testebenen (System-, Integrations- und Unittest).
Neben den Weiterentwicklungsmöglichkeiten gibt es Optimierungsbedarf des
Laufzeitverhaltens. Die Ausführung des Zielsystems wird bei jedem Betreten und
Verlassen einer Methode kurzzeitig angehalten. Dies ist erforderlich, damit der
Prototyp die aktuellen Programmereignisse auswerten kann. Diese häufigen Unterbrechungen bleiben nicht ohne Auswirkung auf die Laufzeitgeschwindigkeit.
Literaturverzeichnis
Balzert 1996 BALZERT, Helmut: Lehrbuch der Softwaretechnik: SoftwareEntwicklung. Spektrum Akademischer Verlag, 1996
Balzert 1998 BALZERT, Helmut: Lehrbuch der Softwaretechnik: SoftwareManagement Sofware-Qualitätssicherung Unternehmensmodellierung. Spektrum Akademischer Verlag, 1998
Beck 1999
1999
B ECK, Kent: extreme Programming explained. Addisson Wesley,
Böcker und Herczeg 1990a B ÖCKER, Heinz-Dieter ; H ERCZEG, J.: Track a trace construction kit. In: Conference proceedings on Empowering people
: Human factors in computing system: special issue of the SIGCHI Bulletin,
ACM Press, 1990, S. 415–422. – ISBN 0-201-50932-6
Böcker und Herczeg 1990b B ÖCKER, Heinz-Dieter ; H ERCZEG, Jürgen: What
tracers are made of. In: Proceedings of the European conference on objectoriented programming on Object-oriented programming systems, languages,
and applications, ACM Press, 1990, S. 89–99. – ISBN 0-201-52430-X
Ducasse 2001 D UCASSE, Stephane: Reengineering Object-Oriented Applications. Université Pierre et Marie Curie (Paris 6), Habilitation à diriger des recherches en Informatiques. September 2001
Gamma u. a. 1996 G AMMA, Erich ; H ELM, Richard ; J OHNSON, Ralph ; V LIS SIDES, John: Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software. Addidon-Wesley, 1996
Jeckle 2001 J ECKLE, Mario: Objektorientierter Modellaustausch und XMLSpracherzeugung mit XMI, Vortrag auf Konferenz OOP 2001 in München.
2001. – URL http://www.jeckle.de/publikat.htm. – Zugriffsdatum:
11.03.2003
50
LITERATURVERZEICHNIS
51
Kilberth u. a. 1994 K ILBERTH, Klaus ; G RYCZAN, Guido ; Z ÜLLIGHOVEN,
Heinz: Objektorientierter Anwendungsentwicklung: Konzepte, Strategien, Erfahrungen. Vieweg, 1994
Kleyn und Gingrich 1988 K LEYN, Michael F. ; G INGRICH, Paul C.: GraphTrace – understanding object-oriented systems using concurrently animated
views. In: Conference proceedings on Object-oriented programming systems,
languages and applications, ACM Press, 1988, S. 191–205. – ISBN 0-89791284-5
Lehman 1996
L EHMAN, M.M.:
Laws of Software Evolution Revisited.
In: EWSPT96, Springer-Verlag, Oktober 1996, S. 108–124. –
URL http://www.doc.ic.ac.uk/˜mml/feast2/papers/pdf/556.pdf. –
Zugriffsdatum: 10.03.2003
Liggesmeyer 2002 L IGGESMEYER, Peter: Software-Qualität: Testen, Anaylsieren und Verifizieren von Software. Spektrum Akademischer Verlag, 2002
Mitterndorfer 1997 M ITTERNDORFER, Josef: Smalltalk: Sprachen, Klassen,
Programmierwerkzeuge. Bd. 2. Addison-Wesley-Longman, 1997
OMG 2000
O BJECT M ANAGEMENT G ROUP: OMG XML Metadata Interchange (XMI) Specification: Version 1.1. November 2000. – URL
http://www.omg.org/cgi-bin/doc?formal/00-11-02. – Zugriffsdatum:
15.03.1003
OMG 2001
O BJECT M ANAGEMENT G ROUP:
OMG Unified Modeling Language Specification: Version 1.4.
September 2001. –
URL
http://www.omg.org/cgi-bin/doc?formal/01-09-67. – Zugriffsdatum:
15.03.1003
Parnas 1972 PARNAS, D. L.: On the criteria to be used in decomposing systems
into modules. In: Communications of the ACM 15 (1972), Nr. 12, S. 1053–
1058. – ISSN 0001-0782
Reiss und Renieris 2000 R EISS, Steven P. ; R ENIERIS, Manos: Generating
Java trace data. In: Proceedings of the ACM 2000 conference on Java Grande,
ACM Press, 2000, S. 71–77. – ISBN 1-58113-288-3
Renaud 2000 R ENAUD, Karen: HERCULE: Non-invasively Tracking Java
Component-Based Application Activity. In: ECOOP 2000 - Object-Oriented
Programming, Springer-Verlag, 2000, S. 447–471. – ISBN 3-540-67660-0
LITERATURVERZEICHNIS
52
Rupp 2001 RUPP, Chris ; SOPHIST GROUP: Requirements-Engineering und
-management. Carl Hanser Verlag, 2001
Sneed und Winter 2002 S NEED, Harry M. ; W INTER, Mario: Testen objektorientierter Software. Carl Hanser Verlag, 2002
Starke 2000 S TARKE, Gernot: Effektive Software-Architekturen. Carl Hanser
Verlag, 2000
Systä 2000a S YST Ä, Tarja: Understanding the Behavior of Java Programs. In:
Proc. of the 7th Working Conference on Reverse Engineering (WCRE 2000),
URL http://www.cs.tut.fi/˜tsysta/publications2.html. – Zugriffsdatum: 16.03.2003, November 2000, S. 214–223
Systä 2000b S YST Ä, Tarja: Static and Dynamic Reverse Engineering Techniques for Java Software Systems, University of Tampere, Dissertation, Mai 2000
Winter 1999 W INTER, Mario: Qualitätssicherung für objektorientierte Software: Anforderungsermittlung und Test gegen die Anforderungsspezifikation,
Fachbereich Informatik der FernUniversität - Gesamthochschule - in Hagen,
Dissertation, September 1999
Winter 2000 W INTER, Mario: Ein interaktionsbasiertes Modell für den objektorientierten Integrations- und Regressionstest. In: Informatik Forschung und
Entwicklung 15 (2000), Nr. 3
Zeller und Krinke 2000 Z ELLER, Andreas ; K RINKE, Jens: Programmierwerkzeuge: Versionskontrolle - Konstruktion - Testen - Fehlersuche unter Linux/Unix. dpunkt.Verlag, 2000
Züllighoven 1998 Z ÜLLIGHOVEN, Heinz: Das objektorientierte Konstruktionshandbuch. dpunkt.verlag, 1998
Webseiten
URL-Bcel
http://jakarta.apache.org/bcel/index.html
Zugriffsdatum: 15.03.2003
URL-Eclipse
http://www.eclipse.org
Zugriffsdatum: 15.03.2003
URL-Java
http://java.sun.com
Zugriffsdatum: 15.03.2003
LITERATURVERZEICHNIS
53
URL-JPDA
http://java.sun.com/products/jpda
Zugriffsdatum: 15.03.2003
URL-JUnit
http://www.junit.org/index.htm
Zugriffsdatum: 15.03.2003
URL-Log4J
http://jakarta.apache.org/log4j/docs/index.html
Zugriffsdatum: 15.03.2003
URL-Novosoft
http://nsuml.sourceforge.net
Zugriffsdatum: 15.03.2003
URL-Poseidon
http://www.gentleware.com
Zugriffsdatum: 15.03.2003
URL-Rigi
http://www.rigi.csc.uvic.ca/index.html
Zugriffsdatum: 16.03.2003
URL-SCED
http://www.cs.tut.fi/ tsysta/sced/
Zugriffsdatum: 16.03.2003
URL-Xerces
http://xml.apache.org/xerces-j/index.html
Zugriffsdatum: 15.03.2003
Glossar
Abstraktion Eine Verallgemeinerung durch Herausheben des Wesentlichen. Durch
Abstrahierung vom Konkreten entsteht ein Modell der realen Welt. Abstraktionseben geben den Grad bzw. die Abstufung der Abstraktion an (vgl. Balzert 1998, S.559)
Aktionsmonitor Durch den Prototypen zur Verfügung gestelltes Benutzungselement, das den Systemtrace des Zielsystems zur Laufzeit anzeigt.
Artefakt Ein durch menschliches Können geschaffenes künstliches Erzeugnis.
(vgl. Balzert 1998, S.564)
Beobachtungsfilter Durch den Benutzer ausgewählte Menge von Klassen und
Methoden, die bei der Programmausführung beobachtet werden sollen.
Blackbox Interne Systemstrukturen wie z.B. Programmcode oder Modulstruktur
sind nach außen nicht sichtbar.
Botschaftenfluss Die Folge der Botschaften in einem objektorientierten Programm,
die sich die Objekte senden.
Dynamische Struktur −→ Struktur
Dynamisches Testen In einem dynamischen Test wird die Software mit konkreten Testdaten versehen, ausgeführt und beobachtet.
Instrumentierung Manipulation bzw. Anreicherung des Quellcodes um Anweisungen, mit denen Informationen über den Programmablauf erzeugt werden.
Interaktionsfolge Eine Sequenz einzelner Interaktionsschritte in einem Kommunikationsprozess.
Komponentenbrowser Vom Prototypen zur Verfügung gestellte Benutzungseinheit zur Bearbeitung des Beobachtungsfilters und zur Auswahl der Startklasse des Programms.
54
Laufzeitverhalten Die in einem Anwendungsprogramm stattfindenden Vorgänge
und Aktionen zur Ausführungszeit.
Logging Aufzeichnung bestimmter Informationen verschiedener Programmvorgänge
in ein Protokoll, so dass im Nachhinein Aussagen über das Programmverhalten gemacht werden können.
Metrik Messung bzw. Quantifizierung bestimmter Eigenschaften eines Softwaresystems.
Metamodell Festlegung von Elementen, deren Eigenschaften und Beziehungen.
Diese Festlegung bildet eine Beschreibungssprache für bestimmte Modelle.
Modell Durch Abstraktion erhaltene Repräsentation eines realen Sachverhalts,
die dessen Strukturen und Eigenschaften beschreibt.
Programmverfolgung Beobachtung der einzelnen Ausführungsschritte eines Programms zur Laufzeit.
Rekonstruktion Wiedergewinnung bzw. Nachbildung einer Systemarchitektur
unter Benutzung verschiedener Informationsquellen: Quellcode, Entwurfsdokumente, Beobachtung des Programmverhaltens u.a.
Reflexionsmechanismus Ein Mittel zur Feststellung und Nutzung bestimmter
Eigenschaften der Elemente eines Programms (Objekte, Klassen, Methoden u.a.) zur Programmlaufzeit.
Struktur Die Ausprägung eines Systems unter einem bestimmten Aspekt. Je
nach Abstraktion kann ein System unterschiedliche Strukturen aufweisen.
Die statische Struktur abstrahiert vom Laufzeitverhalten und beschreibt die
systemkonstituierenden Elemente und deren Beziehungen. Die dynamische
Struktur ergibt sich durch die Systemaktiviäten. Als Struktur eines Systems
wird auch dessen Architektur bezeichnet. (Balzert 1998, S.567f.), (Starke
2000, S.22)
Szenario Beschreibt den Ablauf einer Interaktionsfolge in einem Kommunikationsprozess.
Systemtrace Enthält die Menge der zur Ausführungszeit beobachteten Programmvorgänge in der Reihenfolge ihres Auftretens.
Whitebox Interne Details eines Systems sind bekannt und können benutzt werden.
Zielsystem Eine mit dem Prototyp zu untersuchende Anwendung, deren dynamische Struktur rekonstruiert werden soll.
55
Anhang A
Fachliches Modell
Die Abbildung A.1 zeigt das konzeptionelle Fachmodell einer in der Programmiersprache Java implementierten Anwendung.
Abbildung A.1: Konzeptionelles Fachmodell
56
Anhang B
Fallbeispiel
B.1
Quellcode der Klasse PersonTest
package testapp;
import junit.framework.*;
public class PersonTest extends TestCase {
public PersonTest(String name) {
super(name);
}
public void setUp() {
}
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new PersonTest("testInitialization"));
return suite;
}
public void testInitialization() {
Person aPerson = new Person();
assertNull(aPerson.getVorname());
assertNull(aPerson.getGeburtstag());
}
public static void main(String args[]) {
junit.swingui.TestRunner.run(PersonTest.class);
}
}
57
ANHANG B. FALLBEISPIEL
B.2
Quellcode der Klasse Person
package testapp;
import java.util.Date;
public class Person {
private Date geburtstag;
private String vorname = "unknown";
public Person() {
}
public Date getGeburtstag() {
return geburtstag;
}
public void setGeburtstag(Date geburtstag) {
this.geburtstag = geburtstag;
}
public String getVorname() {
return vorname;
}
public void setVorname(String vorname) {
this.vorname = vorname;
}
}
58
ANHANG B. FALLBEISPIEL
B.3
Systemtrace des Fallbeispiels
>>MAIN[0] testapp.PersonTest.main (Source: PersonTest.java[28])
|
args: args=instance of java.lang.String[0] (id=75)
|>>STATIC[1] testapp.PersonTest.suite (Source: PersonTest.java[16])
||
args:
||>>CREATE[2] testapp.PersonTest (Source: PersonTest.java[8])
|||
args: name="testInitialization"
|||
receiver: testapp.PersonTest(id=1102)
|||>>CREATE[3] junit.framework.TestCase (Source: TestCase.java[86])
||||
args: name="testInitialization"
||||
receiver: testapp.PersonTest(id=1102)
|||<<CREATE[4] junit.framework.TestCase
|||
state:class$0=null fName="testInitialization"
||<<CREATE[5] testapp.PersonTest
||
state:class$0=null fName="testInitialization"
|<<STATIC[6] testapp.PersonTest.suite
|
rettype: junit.framework.Test retval: N.A.
|>>CALL[7] junit.framework.TestCase.toString (Source: TestCase.java[181])
||
args:
||
receiver: testapp.PersonTest(id=1102)
||
state: class$0=null fName="testInitialization"
||>>CALL[8] junit.framework.TestCase.getName (Source: TestCase.java[188])
|||
args:
|||
receiver: testapp.PersonTest(id=1102)
|||
state: class$0=null fName="testInitialization"
||<<CALL[9] junit.framework.TestCase.getName
||
rettype: java.lang.String retval: N.A.
||
state: class$0=null fName="testInitialization"
|<<CALL[10] junit.framework.TestCase.toString
|
rettype: java.lang.String retval: N.A.
|
state: class$0=null fName="testInitialization"
<<MAIN[11] testapp.PersonTest.main
rettype: void retval:
>>CALL[12] junit.framework.TestCase.run (Source: TestCase.java[118])
|
args: result=instance of junit.framework.TestResult(id=1164)
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
|>>CALL[13] junit.framework.TestCase.countTestCases (Source: TestCase.java[93])
||
args:
||
receiver: testapp.PersonTest(id=1102)
||
state: class$0=null fName="testInitialization"
|<<CALL[14] junit.framework.TestCase.countTestCases
|
rettype: int retval: N.A.
|
state: class$0=null fName="testInitialization"
|>>CALL[15] junit.framework.TestCase.toString (Source: TestCase.java[181])
||
args:
||
receiver: testapp.PersonTest(id=1102)
||
state: class$0=null fName="testInitialization"
|<<CALL[16] junit.framework.TestCase.toString
|
rettype: java.lang.String retval: N.A.
|
state: class$0=null fName="testInitialization"
|>>CALL[17] junit.framework.TestCase.runBare (Source: TestCase.java[125])
||
args:
||
receiver: testapp.PersonTest(id=1102)
||
state: class$0=null fName="testInitialization"
||>>CALL[18] testapp.PersonTest.setUp (Source: PersonTest.java[13])
|||
args:
|||
receiver: testapp.PersonTest(id=1102)
|||
state: class$0=null fName="testInitialization"
||<<CALL[19] testapp.PersonTest.setUp
59
ANHANG B. FALLBEISPIEL
||
rettype: void retval:
||
state: class$0=null fName="testInitialization"
||>>CALL[20] junit.framework.TestCase.runTest (Source: TestCase.java[138])
|||
args:
|||
receiver: testapp.PersonTest(id=1102)
|||
state: class$0=null fName="testInitialization"
|||>>STATIC[21] junit.framework.Assert.assertNotNull (Source: Assert.java[213])
||||
args: object="testInitialization"
||||>>STATIC[22] junit.framework.Assert.assertNotNull (Source: Assert.java[220])
|||||
args: object="testInitialization" message=null
|||||>>STATIC[23] junit.framework.Assert.assertTrue (Source: Assert.java[19])
||||||
args: message=null condition=true
|||||<<STATIC[24] junit.framework.Assert.assertTrue
|||||
rettype: void retval:
||||<<STATIC[25] junit.framework.Assert.assertNotNull
||||
rettype: void retval:
|||<<STATIC[26] junit.framework.Assert.assertNotNull
|||
rettype: void retval:
|||>>CALL[27] testapp.PersonTest.testInitialization (Source: PersonTest.java[22])
||||
args:
||||
receiver: testapp.PersonTest(id=1102)
||||
state: class$0=null fName="testInitialization"
||||>>CREATE[28] testapp.Person (Source: Person.java[10])
|||||
args:
|||||
receiver: testapp.Person(id=1167)
||||<<CREATE[29] testapp.Person
||||
state:geburtstag=null vorname="unknown"
||||>>CALL[30] testapp.Person.getVorname (Source: Person.java[22])
|||||
args:
|||||
receiver: testapp.Person(id=1167)
|||||
state: geburtstag=null vorname="unknown"
||||<<CALL[31] testapp.Person.getVorname
||||
rettype: java.lang.String retval:
||||
state: geburtstag=null vorname="unknown"
||||>>STATIC[32] junit.framework.Assert.assertNull (Source: Assert.java[226])
|||||
args: object="unknown"
|||||>>STATIC[33] junit.framework.Assert.assertNull (Source: Assert.java[233])
||||||
args: object="unknown" message=null
||||||>>STATIC[34] junit.framework.Assert.assertTrue (Source: Assert.java[19])
|||||||
args: message=null condition=false
|||||||>>STATIC[35] junit.framework.Assert.fail (Source: Assert.java[47])
||||||||
args: message=null
||||||||>>CALL[36] junit.framework.TestCase.tearDown (Source: TestCase.java[176])
|||||||||
args:
|||||||||
receiver: testapp.PersonTest(id=1102)
|||||||||
state: class$0=null fName="testInitialization"
||||||||<<CALL[37] junit.framework.TestCase.tearDown
||||||||
rettype: void retval:
||||||||
state: class$0=null fName="testInitialization"
||||||||>>CALL[38] junit.framework.TestCase.toString (Source: TestCase.java[181])
|||||||||
args:
|||||||||
receiver: testapp.PersonTest(id=1102)
|||||||||
state: class$0=null fName="testInitialization"
||||||||<<CALL[39] junit.framework.TestCase.toString
||||||||
rettype: java.lang.String retval: N.A.
||||||||
state: class$0=null fName="testInitialization"
>>CALL[40] junit.framework.TestCase.countTestCases (Source: TestCase.java[93])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[41] junit.framework.TestCase.countTestCases
rettype: int retval: N.A.
60
ANHANG B. FALLBEISPIEL
state: class$0=null fName="testInitialization"
>>CALL[42] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[43] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[44] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[45] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[46] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[47] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[48] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[49] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[50] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[51] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[52] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[53] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
|||||||<<CALL[54] junit.framework.TestCase.run
|||||||
rettype: void retval:
|||||||
state: class$0=null fName="testInitialization"
>>CALL[55] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[56] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[57] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[58] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[59] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
61
ANHANG B. FALLBEISPIEL
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[60] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
>>CALL[61] junit.framework.TestCase.toString (Source: TestCase.java[181])
|
args:
|
receiver: testapp.PersonTest(id=1102)
|
state: class$0=null fName="testInitialization"
<<CALL[62] junit.framework.TestCase.toString
rettype: java.lang.String retval: N.A.
state: class$0=null fName="testInitialization"
62
ANHANG B. FALLBEISPIEL
B.4
63
Metrik zur Messung der Programmaktivitäten
-----------------------------class: testapp.Person total: 1
-----------------------------1 : public java.lang.String getVorname()
0 : public void setGeburtstag(java.util.Date)
0 : public void setVorname(java.lang.String)
0 : public java.util.Date getGeburtstag()
---------------------------------class: testapp.PersonTest total: 3
---------------------------------1 : public void testInitialization()
0 : public void main(java.lang.String[])
1 : public junit.framework.Test suite()
1 : public void setUp()
-------------------------------------class: junit.framework.Assert total: 7
-------------------------------------0 : public void assertSame(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertSame(java.lang.Object, java.lang.Object)
2 : public void assertTrue(java.lang.String, java.lang.String)
0 : public void assertTrue(boolean)
0 : public void assertEquals(long, long)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(float, float, float)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(double, double, double)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(java.lang.String, java.lang.String)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(java.lang.Object, java.lang.Object)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(boolean, boolean)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(byte, byte)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(char, char)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(short, short)
0 : public void assertEquals(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertEquals(int, int)
0 : public void assertFalse(java.lang.String, java.lang.String)
0 : public void assertFalse(boolean)
1 : public void fail(java.lang.String)
0 : public void fail()
0 : java.lang.String format(java.lang.String, java.lang.String, java.lang.String)
0 : private void failSame(java.lang.String)
1 : public void assertNotNull(java.lang.Object)
1 : public void assertNotNull(java.lang.String, java.lang.String)
0 : private void failNotSame(java.lang.String, java.lang.String, java.lang.String)
0 : public void assertNotSame(java.lang.Object, java.lang.Object)
0 : public void assertNotSame(java.lang.String, java.lang.String, java.lang.String)
0 : private void failNotEquals(java.lang.String, java.lang.String, java.lang.String)
1 : public void assertNull(java.lang.Object)
1 : public void assertNull(java.lang.String, java.lang.String)
----------------------------------------class: junit.framework.TestCase total: 20
----------------------------------------0 : protected void setUp()
ANHANG B. FALLBEISPIEL
0 : protected junit.framework.TestResult createResult()
1 : protected void tearDown()
1 : public java.lang.String getName()
0 : public void setName(java.lang.String)
0 : public junit.framework.TestResult run()
1 : public void run(junit.framework.TestResult)
2 : public int countTestCases()
13 : public java.lang.String toString()
1 : protected void runTest()
1 : public void runBare()
64
ANHANG B. FALLBEISPIEL
B.5
65
XMI-Modell der Interaktionen des Fallbeispiels
<?xml version="1.0" standalone="yes"?>
<XMI xmi.version="1.1" xmlns:UML="omg.org/UML/1.4">
<XMI.header>
<XMI.documentation>
<XMI.exporter>ru.novosoft.uml.impl.UMLRepositoryImplXMIWriter</XMI.exporter>
</XMI.documentation>
</XMI.header>
<XMI.content>
<UML:Model xmi.id="a0" isRoot="false" isLeaf="false" isAbstract="false" name="default" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Package xmi.id="a1" isRoot="false" isLeaf="false" isAbstract="false" name="repository" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Class xmi.id="a2" isRoot="false" isLeaf="false"
isAbstract="false" name="Main" isSpecification="false"
isActive="false"></UML:Class>
</UML:Namespace.ownedElement>
</UML:Package>
<UML:Object xmi.id="a3" classifier="a2" linkEnd="a4 a5 a6" name="anonyme" isSpecification="false"></UML:Object>
<UML:Package xmi.id="a7" isRoot="false" isLeaf="false" isAbstract="false" name="testapp" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Class xmi.id="a8" isRoot="false" isLeaf="false"
isAbstract="false" name="PersonTest" isSpecification="false"
isActive="false">
<UML:Classifier.feature>
<UML:Operation xmi.id="a9" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier" name="suite"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a10" kind="return" type="a11" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a12" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="setUp"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a13" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a15" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance"
name="testInitialization" visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a16" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
</UML:Classifier.feature>
</UML:Class>
<UML:Class xmi.id="a17" isRoot="false" isLeaf="false"
isAbstract="false" name="Person" isSpecification="false" isActive="false">
<UML:Classifier.feature>
<UML:Operation xmi.id="a18" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="getVorname"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a19" kind="return" type="a20" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
</UML:Classifier.feature>
</UML:Class>
</UML:Namespace.ownedElement>
</UML:Package>
<UML:Object xmi.id="a21" classifier="a8" linkEnd="a22 a23 a24 a25 a26
a27 a28 a29 a30 a31 a32 a33 a34 a35 a36 a37 a38 a39 a40 a41 a42 a43 a44 a45
a46 a47" name="<<class>>" isSpecification="false"></UML:Object>
<UML:Package xmi.id="a48" isRoot="false" isLeaf="false" isAbstract="false" name="junit" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Package xmi.id="a49" isRoot="false" isLeaf="false" isAbstract="false" name="framework" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Class xmi.id="a11" isRoot="false" isLeaf="false"
isAbstract="false" name="Test" isSpecification="false"
isActive="false"></UML:Class>
<UML:Class xmi.id="a50" isRoot="false" isLeaf="false"
isAbstract="false" name="TestCase" isSpecification="false" isActive="false">
<UML:Classifier.feature>
<UML:Operation xmi.id="a51" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="toString"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a52" kind="return" type="a20" name="return" isSpecification="false"></UML:Parameter>
ANHANG B. FALLBEISPIEL
66
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a53" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="getName"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a54" kind="return" type="a20" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a55" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="run"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a56" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a57" type="a58" name="arg1" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a59" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance"
name="countTestCases" visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a60" kind="return" type="a61" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a62" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="runBare"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a63" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a64" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="runTest"
visibility="protected" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a65" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a66" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="instance" name="tearDown"
visibility="protected" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a67" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
</UML:Classifier.feature>
</UML:Class>
<UML:Class xmi.id="a58" isRoot="false" isLeaf="false"
isAbstract="false" name="TestResult" isSpecification="false"
isActive="false"></UML:Class>
<UML:Class xmi.id="a68" isRoot="false" isLeaf="false"
isAbstract="false" name="Assert" isSpecification="false" isActive="false">
<UML:Classifier.feature>
<UML:Operation xmi.id="a69" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier"
name="assertNotNull" visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a70" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a71" type="a72" name="arg1" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a73" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier"
name="assertNotNull" visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a74" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a75" type="a20" name="arg1" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a76" type="a72" name="arg2" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a77" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier" name="assertTrue"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a78" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a79" type="a20" name="arg1" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a80" type="a81" name="arg2" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a82" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier" name="assertNull"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
ANHANG B. FALLBEISPIEL
67
<UML:Parameter xmi.id="a83" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a84" type="a72" name="arg1" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a85" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier" name="assertNull"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a86" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a87" type="a20" name="arg1" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a88" type="a72" name="arg2" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
<UML:Operation xmi.id="a89" isQuery="false" isRoot="false"
isLeaf="false" isAbstract="false" ownerScope="classifier" name="fail"
visibility="public" isSpecification="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter xmi.id="a90" kind="return" type="a14" name="return" isSpecification="false"></UML:Parameter>
<UML:Parameter xmi.id="a91" type="a20" name="arg1" isSpecification="false"></UML:Parameter>
</UML:BehavioralFeature.parameter>
</UML:Operation>
</UML:Classifier.feature>
</UML:Class>
</UML:Namespace.ownedElement>
</UML:Package>
</UML:Namespace.ownedElement>
</UML:Package>
<UML:CallAction xmi.id="a92" operation="a9" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a93" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a4" instance="a3" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a22" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a94" sender="a3" receiver="a21"
communicationLink="a93" dispatchAction="a92" name="1"
isSpecification="false"></UML:Stimulus>
<UML:Object xmi.id="a95" classifier="a8" linkEnd="a96 a97 a98 a99 a100
a101 a102 a103 a104 a105 a106 a107 a108 a109 a110 a111 a112 a113 a114 a115
a116 a117 a118 a119 a120 a121 a122 a123 a124 a125 a126 a127" name="1102"
isSpecification="false"></UML:Object>
<UML:Package xmi.id="a128" isRoot="false" isLeaf="false" isAbstract="false" name="java" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Package xmi.id="a129" isRoot="false" isLeaf="false" isAbstract="false" name="lang" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Class xmi.id="a20" isRoot="false" isLeaf="false"
isAbstract="false" name="String" isSpecification="false"
isActive="false"></UML:Class>
<UML:Class xmi.id="a72" isRoot="false" isLeaf="false"
isAbstract="false" name="Object" isSpecification="false"
isActive="false"></UML:Class>
</UML:Namespace.ownedElement>
</UML:Package>
</UML:Namespace.ownedElement>
</UML:Package>
<UML:CallAction xmi.id="a130" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a131" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a5" instance="a3" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a96" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a132" sender="a3" receiver="a95"
communicationLink="a131" dispatchAction="a130" name="7"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a133" operation="a53" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a134" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a97" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a98" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a135" sender="a95" receiver="a95"
communicationLink="a134" dispatchAction="a133" name="8"
isSpecification="false"></UML:Stimulus>
<UML:Class xmi.id="a14" isRoot="false" isLeaf="false"
isAbstract="false" name="void" isSpecification="false"
isActive="false"></UML:Class>
<UML:CallAction xmi.id="a136" operation="a55" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a137" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a6" instance="a3" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a99" instance="a95" isSpecification="false"></UML:LinkEnd>
ANHANG B. FALLBEISPIEL
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a138" sender="a3" receiver="a95"
communicationLink="a137" dispatchAction="a136" name="11"
isSpecification="false"></UML:Stimulus>
<UML:Class xmi.id="a61" isRoot="false" isLeaf="false"
isAbstract="false" name="int" isSpecification="false"
isActive="false"></UML:Class>
<UML:CallAction xmi.id="a139" operation="a59" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a140" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a100" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a101" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a141" sender="a95" receiver="a95"
communicationLink="a140" dispatchAction="a139" name="12"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a142" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a143" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a102" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a103" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a144" sender="a95" receiver="a95"
communicationLink="a143" dispatchAction="a142" name="14"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a145" operation="a62" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a146" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a104" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a105" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a147" sender="a95" receiver="a95"
communicationLink="a146" dispatchAction="a145" name="16"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a148" operation="a12" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a149" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a106" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a107" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a150" sender="a95" receiver="a95"
communicationLink="a149" dispatchAction="a148" name="17"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a151" operation="a64" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a152" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a108" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a109" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a153" sender="a95" receiver="a95"
communicationLink="a152" dispatchAction="a151" name="19"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a154" operation="a69" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a155" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a110" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a23" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a156" sender="a95" receiver="a21"
communicationLink="a155" dispatchAction="a154" name="20"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a157" operation="a73" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a158" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a24" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a25" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a159" sender="a21" receiver="a21"
communicationLink="a158" dispatchAction="a157" name="21"
isSpecification="false"></UML:Stimulus>
<UML:Class xmi.id="a81" isRoot="false" isLeaf="false"
isAbstract="false" name="boolean" isSpecification="false"
isActive="false"></UML:Class>
<UML:CallAction xmi.id="a160" operation="a77" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a161" isSpecification="false">
68
ANHANG B. FALLBEISPIEL
<UML:Link.connection>
<UML:LinkEnd xmi.id="a26" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a27" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a162" sender="a21" receiver="a21"
communicationLink="a161" dispatchAction="a160" name="22"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a163" operation="a15" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a164" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a111" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a112" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a165" sender="a95" receiver="a95"
communicationLink="a164" dispatchAction="a163" name="26"
isSpecification="false"></UML:Stimulus>
<UML:Object xmi.id="a166" classifier="a17" linkEnd="a167" name="1167" isSpecification="false"></UML:Object>
<UML:CallAction xmi.id="a168" operation="a18" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a169" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a113" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a167" instance="a166" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a170" sender="a95" receiver="a166"
communicationLink="a169" dispatchAction="a168" name="29"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a171" operation="a82" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a172" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a114" instance="a95" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a28" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a173" sender="a95" receiver="a21"
communicationLink="a172" dispatchAction="a171" name="31"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a174" operation="a85" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a175" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a29" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a30" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a176" sender="a21" receiver="a21"
communicationLink="a175" dispatchAction="a174" name="32"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a177" operation="a77" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a178" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a31" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a32" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a179" sender="a21" receiver="a21"
communicationLink="a178" dispatchAction="a177" name="33"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a180" operation="a89" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a181" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a33" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a34" instance="a21" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a182" sender="a21" receiver="a21"
communicationLink="a181" dispatchAction="a180" name="34"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a183" operation="a66" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a184" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a35" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a115" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a185" sender="a21" receiver="a95"
communicationLink="a184" dispatchAction="a183" name="35"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a186" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a187" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a36" instance="a21" isSpecification="false"></UML:LinkEnd>
69
ANHANG B. FALLBEISPIEL
<UML:LinkEnd xmi.id="a116" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a188" sender="a21" receiver="a95"
communicationLink="a187" dispatchAction="a186" name="37"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a189" operation="a59" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a190" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a37" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a117" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a191" sender="a21" receiver="a95"
communicationLink="a190" dispatchAction="a189" name="39"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a192" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a193" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a38" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a118" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a194" sender="a21" receiver="a95"
communicationLink="a193" dispatchAction="a192" name="41"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a195" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a196" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a39" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a119" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a197" sender="a21" receiver="a95"
communicationLink="a196" dispatchAction="a195" name="43"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a198" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a199" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a40" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a120" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a200" sender="a21" receiver="a95"
communicationLink="a199" dispatchAction="a198" name="45"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a201" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a202" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a41" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a121" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a203" sender="a21" receiver="a95"
communicationLink="a202" dispatchAction="a201" name="47"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a204" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a205" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a42" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a122" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a206" sender="a21" receiver="a95"
communicationLink="a205" dispatchAction="a204" name="49"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a207" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a208" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a43" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a123" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a209" sender="a21" receiver="a95"
communicationLink="a208" dispatchAction="a207" name="51"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a210" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a211" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a44" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a124" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
70
ANHANG B. FALLBEISPIEL
<UML:Stimulus xmi.id="a212" sender="a21" receiver="a95"
communicationLink="a211" dispatchAction="a210" name="54"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a213" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a214" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a45" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a125" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a215" sender="a21" receiver="a95"
communicationLink="a214" dispatchAction="a213" name="56"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a216" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a217" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a46" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a126" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a218" sender="a21" receiver="a95"
communicationLink="a217" dispatchAction="a216" name="58"
isSpecification="false"></UML:Stimulus>
<UML:CallAction xmi.id="a219" operation="a51" isAsynchronous="false" isSpecification="false"></UML:CallAction>
<UML:Link xmi.id="a220" isSpecification="false">
<UML:Link.connection>
<UML:LinkEnd xmi.id="a47" instance="a21" isSpecification="false"></UML:LinkEnd>
<UML:LinkEnd xmi.id="a127" instance="a95" isSpecification="false"></UML:LinkEnd>
</UML:Link.connection>
</UML:Link>
<UML:Stimulus xmi.id="a221" sender="a21" receiver="a95"
communicationLink="a220" dispatchAction="a219" name="60"
isSpecification="false"></UML:Stimulus>
</UML:Namespace.ownedElement>
</UML:Model>
</XMI.content>
</XMI>
71
Anhang C
Installationshinweise
Der Prototyp wurde unter dem Betriebssystem Linux (SuSE 8.1) entwickelt und
getestet. Dadurch kann nicht ausgeschlossen werden, dass bei einem Einsatz unter
Windows plattformbedingte Störungen auftreten. Vorausgesetzt wird eine installierte Java-Ausführungsumgebung in der Version 1.3.1
C.1
Installation
1. Kopieren Sie das Verzeichnis tracer vollständig von der CD in ein Zielverzeichnis Ihrer Wahl auf die Festplatte.
2. Öffnen Sie je nach Betriebssytem die Skriptdatei tracer/bin/tracer.sh
(für Linux) oder tracer\bin\tracer.bat für Windows in einem Editor.
3. Setzen Sie den Wert der Variablen APPDIR auf den absoluten Pfad des im
Schritt 1 benutzten Zielverzeichnisses und speichern Sie die Datei.
4. Je nach verwendetem Betriebssystem wird der Prototyp durch Aufruf der
entsprechenden Skriptdatei gestartet.
C.2
Benutzungshinweise
1. In dem Verzeichnis tracer/sample-apps/classes befinden sich zwei
Pakete mit Testklassen. Geben Sie das genannte Verzeichnis als Klassenpfad an. Klicken Sie hierfür auf den oberen Button <- in der Gruppe Zielsystem.
Wählen Sie in dem erscheinenden Dialog das Verzeichnis aus. Daraufhin
wird es in dem Textfeld angezeigt.
72
ANHANG C. INSTALLATIONSHINWEISE
73
2. Mit einem Klick auf den Button Laden wird die Analyse der statischen
Struktur ausgelöst. Als Ergebnis werden die ermittelten Pakete, Klassen und
Methoden angezeigt.
3. Beachten Sie bitte im Folgenden den Unterschied zwischen Markieren und
Selektieren: Eine Markierung wird durch Benutzung der Checkbox einer
Zeile erreicht. Eine Selektion ist die Auswahl einer Zeile, ohne die Checkbox zu benutzen.
Selektieren Sie nun das Paket testapp. Dadurch werden die in dem Paket
enthaltenen Klassen angezeigt. Markieren Sie die Klassen PersonTest und
Person.
4. Geben Sie die Klasse PersonTest als Startklasse an, indem Sie diese Klasse selektieren und den Button <- in der Gruppe Startklasse anklicken.
Daraufhin wird der Name der Klasse in dem Textfeld angezeigt.
5. Aktivieren Sie den Tab Aktionsmonitor. In der Gruppe Programm sollte
der Button Start aktiviert sein. Mit Klick auf diesen Button starten Sie
das Zielsystem. Je nach Leistungsfähigleit der Hardware kann es jetzt einen
Moment dauern, bis JUnit erscheint.
6. Für die Speicherung und den Dateiexport sind entsprechende Einträge in
den Menüs vorgesehen.
Versicherung über die Selbständigkeit
Hiermit versichere ich, dass ich die vorliegende Arbeit im Sinne der Prüfungsordnung nach 24(5) ohne fremde Hilfe selbständig verfasst und nur die angegebenen
Hilfsmittel benutzt habe.
Ort, Datum
Unterschri f t
Herunterladen