Per Mausklick bewerten und Feedback geben
MSDN
MSDN Library
Entwicklerbibliothek
.NET-Entwicklung
.NET Framework
Allgemein
 Im Netz eines Partikelfilters
Im Netz eines Partikelfilters
Veröffentlicht: 31. Okt 2005
Von Joachim H. Fröhlich und Reinhard Wolfinger

Aussagekräftige Last- und Funktionstests von Komponenten decken reale Anwendungen ab. Die Spezifikation von realen Anwendungen für binäre, vielseitig einsetzbare Komponenten ist jedoch alles andere als leicht. Abhilfe versprechen Rekorder, die Benutzungsprofile unter realitätsnahen Bedingungen aufzeichnen. Im Mittelpunkt des Artikels steht "Spyder.NET", ein schlanker, konfigurierbarer Rekorder zum selektiven Aufzeichnen von Benutzungsprofilen binärer .NET-Komponenten. Funktionen und Aufbau von Spyder.NET werden an einer ADO.NET-Anwendung gezeigt.

OBJEKTspektrum


Diesen Artikel können Sie dank freundlicher Unterstützung von OBJEKTspektrum auf MSDN Online lesen. OBJEKTspektrum ist ein Partner von MSDN Online.
Ausgabe 5/2005


Zur Partnerübersichtsseite von MSDN Online

Der Aufzeichnung von Programmabläufen haftet etwas Verdächtiges an. Ein Programmierer, der mit Ausgabeanweisungen den Steuerfluss verfolgt, kann schon einmal in den Verdacht geraten, sich im Programm oder der zu Grunde liegenden Technologie nicht auszukennen oder ein Hacker und Hopper zu sein. Tatsächlich ist diese Methode zur Verfolgung des Steuerflusses mit Ausgabeanweisungen - im Gedenken an die Programmiersprache C auch als "printf-Debugging" bekannt - allen Fortschritten der Softwaretechnik zum Trotz weit verbreitet (vgl. [Coa03]), etwa um unerwünschte Seiteneffekte in Altlasten in den Griff zu bekommen. Auch wir greifen auf diese Methode in Kombination mit anderen Techniken immer wieder zurück. Wir befinden uns damit in guter Gesellschaft, nicht nur beim Debugging von Komponenten, die in Applikationsserver einbettet sind. So heißt es z. B. in [Rob03], S. 717: "To debug these server applications, everyone resorts to the old standby of tracing." Die Framework Class Library (FCL) von .NET bietet dabei unter anderem im Umfeld der Klassen Trace und PerformanceCounter komfortable Unterstützung an.

Zahlreiche Programme benötigen eine systematische, modellgetriebene Ablaufverfolgung etwa auf der Basis von Log- Dateien, ebenso wie Flugzeuge Flugschreiber benötigen. Dazu gehören neben Programmen, die in der Obhut von Applikationsservern laufen, parallele, verteilte und reaktive Programme (vgl. [Gat04]), z.B. Anlagensteuerungen und Informationssysteme, insbesondere solche mit hohen Anforderungen an die Sicherheit, Verfügbarkeit und Zuverlässigkeit. Log- Dateien erweisen sich auch als nützlich zur Fehlerdiagnose in Umgebungen, in denen keine komfortablen Entwicklungs- und Wartungswerkzeuge zur Verfügung stehen (Field Debugging). Und das trifft mehr oder weniger auf alle produktiv eingesetzten (im Release-Modus laufenden) Programme zu. Gerade in diesen Fällen können Log-Dateien mit problemrelevanten, aktuellen und genauen Einträgen die Fehlersuche und das Testen von Fehlerkorrekturen erheblich verkürzen.

Zu diesem Zweck - besonders für die Diagnose von Performance-Engpässen - wurden in Version 2.0 von ADO.NET (die .NET-Variante der "ActiveX Data Objects"- Bibliothek zum Zugriff auf relational organisierte Daten) die Mechanismen zur Ablaufverfolgung erheblich ausgebaut (vgl. [Bea04]). Für die Reproduktion von Benutzungsprofilen im Zuge von automatisierten Last- und Funktionstests von Komponenten - das Thema, mit dem wir uns im Weiteren und ausschließlich im Kontext von .NET beschäftigen - sind diese Mechanismen jedoch nicht ausgelegt und auch nicht geeignet. Ganz allgemein können wir nicht davon ausgehen, dass Komponenten von Haus aus die Erstellung von Benutzungsprofilen unterstützen.

Auf dieser Seite

Profilierung Profilierung
Angebot und Nachfrage Angebot und Nachfrage
Spyder.NET Spyder.NET
Testgegenstand Testgegenstand
Spyder.NET im Einsatz Spyder.NET im Einsatz
Schlussbetrachtung Schlussbetrachtung
Literatur & Links Literatur & Links
Die Autoren Die Autoren

Profilierung

Realitätsnahe Tests von Komponenten gehen von realen Benutzungsprofilen aus. Das gilt insbesondere für Lasttests von Komponenten und nützt der Gestaltung der Architektur von Programmen bereits in frühen Entwicklungsstadien. Von systematischen Komponententests profitieren aber gerade auch Programme, die sich in fortgeschrittenen Produktzyklen befinden, etwa wenn es gilt, für ein Online-Warenhaus festzustellen, wie sich das Antwortzeitverhalten ändert, wenn das Sortiment erweitert wird oder wenn sich Navigationsgewohnheiten von Benutzern durch zusätzliche Informationen über angebotene Waren ändern. Vielseitig verwendbare, für das Antwortzeitverhalten und den Speichergebrauch eines Programms kritische Komponenten von Drittanbietern können nur auf der Grundlage situationsbedingter, realer Benutzungsprofile einigermaßen verlässlich getestet werden. Nach dem Stand der Technik werden in diesen Fällen Rekorder eingesetzt, die typische und kritische Benutzungsprofile automatisch und ohne Mitwirkung oder spezielle Präparation von Testgegenständen aufzeichnen, um den Testaufwand zu verringern. Auch Funktionstests, die nicht über die Schnittstellen von Komponenten hinaus gehen (Black-Box-Tests), können von realen Benutzungsprofilen profitieren, gerade in Anbetracht von Abläufen, die aus guten Gründen hinter den Kulissen versteckt sind (siehe Kasten 1), und der zum Entwicklungszeitpunkt nicht genau bestimmbaren Programmkonfigurationen und Einsatzszenarien. Quantitative und qualitative Analysen von Benutzungsprofilen helfen bei der Bestimmung zu optimierender oder zu testender Komponenten. Automatisierte Vergleiche von aufgezeichneten Benutzungsprofilen beschleunigen Regressionstests. Was ein Rekorder im Einzelnen können soll, hängt natürlich vom Einsatzzweck ab.

Kasten 1:

Von einem Rekorder ans Tageslicht befördert

"Hello Underworld"

Über ein Programm, das "Hello World" auf der Konsole ausgibt, gibt es Nichts Interessantes zu berichten. Es sei denn, man sucht ein entspanntes Thema für die nächste Kaffeepause, will wissen, was hinter den Kulissen der FCL abläuft und hat einen Rekorder á la Spyder.NET zur Hand. Vor der Kaffeepause baten wir kurzerhand verfügbare Kollegen unabhängig voneinander anhand des "Hello World"-Programms um eine Schätzung für die Anzahl der Methoden, die im managed Teil der CLR 2.0, Beta 1 laufen. Die Antworten begannen bei 30 und reichten über 50, 100, 150, 200 bis 1.000, wiederholt kommentiert mit "bestimmt viele". Dann haben wir Spyder.NET 1.381 Methodenaktivierungen zählen lassen, 895 davon vor void static Main(), 467 in Main() für Console.WriteLine und 19 danach. Zu Beginn der Kaffeepause war die Überraschung über die Unterschiede zwischen den vermuteten und tatsächlichen Ergebnissen groß. Da auch unsere Version sich an die für ein ordentliches "Hello World"-Programm gängige Implementierung hielt, kamen wir schließlich zum Wesentlichen, der Suche nach Erklärungen, wobei wir uns auf technische beschränkten. Wir fassen zusammen:

  • Dahinter steckt die im Quelltext nicht sichtbare Standard Application Domain.
  • Der IL-Code der Main-Methode wird beim ersten und einzigen Aufruf von Main in Maschinencode übersetzt, was einmalig zusätzlichen Aufwand verursacht.
  • Vermutlich werden ein paar Objekte erzeugt und freigegeben, obwohl davon im Quelltext nichts zu sehen ist.

Die Aufwände für das Einrichten der Application Domain und die Übersetzung von Methoden (JIT) relativieren sich, wenn "Hello World" gegen ein "Real World"- Programm ausgetauscht wird. Als dann noch Fragen aufgeworfen wurden wie

  • Welche Aufgaben übernimmt der managed Teil (die FCL) im Einzelnen, welche der unmanaged Teil?
  • Ist das von Spyder.NET gewebte Netz dicht?
  • Weiß jemand, wie das in Java läuft?

war die Kaffeepause um. Und wir hatten nun eine eigene Vorstellung davon, was sich hinter dem Satz ".the System.AppDomain.SetupDomain method creates huge amounts of trace output" verbirgt und warum Robbins, von dem diese Aussage stammt, Vor- und Nachspann der Main-Methode für gewöhnlich nicht aufzeichnen lässt und nicht bespricht (vgl. [Rob03], S. 485).

Angebot und Nachfrage

Beginnen wir mit der Nachfrage, um das Angebot seitens .NET besser beurteilen zu können. Wir benötigen einen Rekorder, der Last- und Funktionstests von Komponenten vereinfachen hilft, indem er Benutzungsprofile an Schnittstellen objektorientiert implementierter Komponenten festhält. Unser Augenmerk gilt ausdrükklich nicht klassischen Profilern, die Performanceanalysen unterstützen. Wir benötigen einen Rekorder, der Benutzungsprofile von unveränderten, binären Komponenten ermittelt in Form von

  • Methodenaufrufen ausgewählter Objekte zusammen mit den

  • Werten von Eingangs- und Ausgangsparametern.

Daraus muss ein Codegenerator einen Testtreiber generieren können, dessen Aufgabe im Wesentlichen darin besteht, für jede aufgezeichnete Methode die Werte für Eingangsparameter auf den Call-Stack zu legen, die Methode aufzurufen und die Werte von Ausgangsparametern entgegen zu nehmen.

Die Knappheit der Anforderungen an einen Rekorder kann nicht über den Implementierungsaufwand hinwegtäuschen. Dieser geht zum einen auf die - wenn man so will - natürliche Komplexität von Laufzeitumgebungen zurück. Zum anderen verdeutliche man sich nur einmal die Fülle von Diensten, die speziell die Common Language Runtime (CLR) konform zur Common Language Infrastructure (CLI) erbringt (vgl. [Mil04]). Fassen wir ein paar Punkte zusammen:

  • Werte- und Referenzparameter (Objekte) können flach und tief strukturiert und unter Umständen in Arrays angeordnet sein.

  • Exceptions (abnorme Ausnahmen) ändern den normalen Steuerfluss und den Umgang mit Ausgangsparametern.

  • Klassen können in verschiedenen Assemblies und Anwendungsdomänen (Application Domains, Prozesse der CLR) gleichzeitig geladen sein.

  • Objekte wechseln während einer Programmsitzung im Zuge der Speicherbereinigung ihre Adressen.

  • Die Laufzeitumgebung verwendet während einer Sitzung für verschiedene Objekte dieselben Adressen.

  • Threads werden von der Laufzeitumgebung auf verschiedene Betriebssystem- Threads abgebildet.

  • Typen und Methoden können mit Typen parametrisiert warden (Generizität).

Außerdem erfordert die Wiedergabe realitätsnaher Benutzungsprofile die Aufzeichnung von Datenflüssen. Bei einer Datenbankanwendung beispielsweise macht es in Bezug auf Ressourcengebrauch und Antwortzeitverhalten eben sehr wohl einen Unterschied, ob eine Operation über eine existierende oder eine neue Datenbankverbindung läuft. Daher soll aus einem aufgezeichneten Programmlauf hervorgehen,

  • wann ein Objekt erzeugt oder als Nachrichtenparameter geliefert wird,

  • welches Objekt eine Nachricht empfängt,

  • ob ein neues oder ein existierendes Objekt als Nachrichtenparameter verwendet wird und

  • wann ein Objekt und die damit verbundenen Ressourcen freigegeben werden.

Ein Rekorder, der all diese Anforderungen lediglich für Aufrufe von Methoden an der Schnittstelle einer Komponente umsetzt, der also an der Oberfläche einer Komponente hängen bleibt, unterstützt die Aufzeichnung von Oberflächenprofilen. Ein derartiger Rekorder kann bei hohem Zusatznutzen ohne großen Mehraufwand in das Innere von Komponenten eintauchen und Tiefenprofile (Durchstiche) aufzeichnen. In Form von Tiefenprofilen aufgezeichnete Programmläufe unterstützen bekanntlich speziell das Debugging. Zu guter Letzt muss ein Rekorder, dessen Implementierung in einen Testgegenstand eingreift, grundsätzlich so effizient wie möglich implementiert sein, um nicht Zeitund Speichergrenzen zu überschreiten und damit z. B. Timeouts zu provozieren, die das Verhalten des Testgegenstands unzulässig verändern. Freilich tut sich ein Rekorder, der ein unbegrenztes oder ein kompliziert begrenztes und daher aufwändig zu berechnendes Tiefenprofil aufzeichnet, mit zuletzt genannter Anforderung etwas schwer.

Der skizzierte Rekorder bleibt eine Skizze auf dem Reißbrett, wenn er sich nicht mit einem am Nutzen gemessenen, vernünftigen Aufwand umsetzen lässt. COM+, der nach wie vor allgegenwärtige Vorgänger von .NET, bot diesbezüglich keine Unterstützung, abgesehen von den auf binärer Ebene standardisierten Komponentenschnittstellen (vgl. [Frö01]). Inwieweit unterstützt nun .NET die Implementierung des skizzierten Rekorders? Mit dieser Frage wechseln wir die Seiten von der Nachfrage zum Angebot.

Die Laufzeitumgebung von .NET (die CLR) gewährt verschiedenen Werkzeugen (CLR-Erweiterungen) den Zugang zu einer ganzen Reihe von Daten über das Verhalten eines .NET-Programms an einer funktional mächtigen COM- Schnittstelle. Diese Schnittstelle der CLR wird Profiler- Schnittstelle genannt. Der Name darf jedoch nicht davon ablenken, dass die CLR über diese Schnittstelle weit mehr Funktionen anbietet, als ein gewöhnlicher Profiler zum Ermitteln von Durchlaufzahlen und -zeiten von Programmteilen (Methoden) benötigt. Tabelle 1 vermittelt einen Eindruck von Aufgaben und Umfang dieser Schnittstelle. Eine genaue Analyse zeigt, dass sie auch die für den skizzierten Rekorder nötigen Funktionen enthält1).

COM-Schnittstelle der CLR
Tabelle 1: COM-Schnittstelle der CLR. Auszug aus corprof.idl und cor.h (.NET 2.0, ohne als "überholt" markierte Funktionen). Funktionsresultate werden als Parameter gezählt. Angeführt sind für Spyder.NET relevante Aufgaben.

Die CLR-Schnittstelle ist komplex und ihre Anwendung fehleranfällig. Die Komplexität folgt direkt aus der Breite des Funktionsangebots und der Mächtigkeit der CLR. Die Komplexität ist aber nur zum Teil essenzieller Natur. Der Schnittstelle fehlt Struktur und das erschwert den Umgang mit ihr. Darauf weisen die Zahlen in Tabelle 1 direkt hin.

Betrachten wir z. B. die Profiler-Schnittstelle im engeren Sinn, d. h. ICorProfilerCallBack2 und ICorProfilerInfo2. Diese sind nicht etwa strukturiert nach Begriffen, wie z. B. Thread, Methode oder Assembly, also nach inhaltlichen Kriterien, sondern nach der Zugehörigkeit zur CLR. Funktionen, die die CLR nutzt, um einen Profiler über Laufzeitereignisse zu informieren, sind in ICorProfilerCallBack2 zusammengefasst. Funktionen, über die ein Profiler von der CLR Daten anfordert, sind in ICorProfilerInfo2 zusammengefasst.

In Zahlen drückt sich (unter Einbeziehung der für den Rekorder unabdingbaren Metadaten-Schnittstelle) die flache Struktur so aus, dass sich 171 Funktionen mit zusammen 718 Parametern auf drei Schnittstellen verteilen (ICorProfilerCallBack2, ICorProfilerInfo2, IMetaDataImport2). Hinzu kommt, dass die Funktionen Laufzeitkonstrukte über Kennzeichen in der Form von untypisierten Zeigern identifizieren. Über Gründe für diesen Entwurf können wir nur spekulieren. Möglicherweise ist er einer Ressourcen schonenden Implementierung geschuldet, die ja für einen Profiler notwendig ist.2)

1) Das gilt aber erst seit .NET Version 2.0, Beta 1. Vorgängerversionen waren in Bezug auf die Rekorderfunktionalität unvollständig und zum Teil fehlerhaft (vgl. [Hil05]).

2) Eine inhaltlich feinere Auffächerung der Profiler- Schnittstelle würde zumindest bezüglich COM die Performanz nicht beeinträchtigen. Da alle Aufrufe von Profiler-Funktionen über COM-Schnittstellen laufen, können untypisierte Zeiger in den Parameterlisten mit (Zeigern auf spezifische) COMSchnittstellen ersetzt werden, über die dann weitere Funktionsaufrufe typsicher laufen.

Spyder.NET

Spyder.NET versucht die Lücken zwischen Angebot (CLR-Schnittstelle) und Nachfrage (reale Benutzungsprofile von Komponenten für das Testen und Debugging) zu schließen. Auf der einen Seite abstrahiert Spyder.NET von der CLR, von verschiedenen Versionen der CLR-Schnittstelle und von ihrer technischen Umsetzung in Form einer COM-Schnittstelle.3) Auf der anderen Seite abstrahiert Spyder.NET von konkreten Verwendungszwecken eines Benutzungsprofils, etwa zur Suche von Fehlerursachen anhand von ausgewählten Programmläufen mit mehreren Steuerflüssen (Threads), zur Generierung der Gerüste von Testtreibern oder zur Überwachung von Prozessen. Abbildung 1 zeigt die Architektur von Spyder.NET.

Architektur von Spyder.NET.
Abb. 1: Architektur von Spyder.NET. Organisation der Profiler-Schnittstelle wie in Tab. 1; MM = Memory-Management, CF = Configuration.

Spyders Anwendungsbereich ist die dynamische Struktur eines komponentenbasierten Programms, das sich objektorientierter Implementierungstechniken bedient. Der Kern von Spyder konzentriert sich auf die Ermittlung der Daten, die Objekte über ausgewählte Methoden austauschen. Zu diesem Zweck greift Spyder aus der Profiler-Schnittstelle der CLR die nötigen Funktionen heraus, fasst sie unter geeigneten Namen zusammen, ordnet sie den aus der Sicht des (eingeschränkten) Anwendungsbereiches relevanten Laufzeitkonstrukten zu und richtet dazwischen Navigationsbeziehungen ein. Die Modellelemente sind an der Schnittstelle zwischen Spyder-Kern und Spyder- Anwendung als rein abstrakte Klassen realisiert. Damit erleichtert diese Schnittstelle nicht nur die Navigation im Laufzeitmodell, sondern entkoppelt gleichzeitig Kern und Anwendungen auf effiziente Weise. Abbildung 2 skizziert das Laufzeitmodell (die fachliche Schnittstelle des Spyder- Kerns); die Angaben in Tabelle 2 vermitteln einen Eindruck vom Umfang und von der Stärke der Strukturierung.

Struktur der fachlichen Schnittstelle zwischen Spyder-Kern und Spyder- Anwendungen
Abb. 2: Struktur der fachlichen Schnittstelle zwischen Spyder-Kern und Spyder- Anwendungen. Die Schnittstelle setzt sich zusammen aus Einzelschnittstellen (rein abstrakte Klassen: IEvents, IThread .U bis Z in Abb. 1).

Spyder-Kern- Schnittstelle
Tabelle 2: Spyder-Kern- Schnittstelle; entspricht U bis Z in Abb. 1; Parameter inkludieren Funktionswerte; Schnittstellen ohne Funktionen, z.B. IResult verbessern die Typsicherheit (BS = Basisschnittstellen, AS = abgeleitete Schnittstellen, RS = referenzierte Schnittstellen).

Das fachliche Modell deckt auch Exceptions ab, obwohl das aus der Übersicht in Abbildung 2 nicht direkt hervor geht. Exceptions werden als Ereignisse abgebildet, die Ein- und Austritte in Methoden signalisieren. Dahingehend unterscheiden sich Exceptions nicht von gewöhnlichen Methodenaktivierungen und -deaktivierungen. Lediglich die normale Paarung von Ein- und Austrittsereignissen gilt nicht für jene Methoden, die eine Exception entgegengesetzt zur Richtung des normalen Steuerflusses durchfließt. Im Zuge eines Exception-Flusses von der CLR ausgelöste Automatismen erscheinen als normale Methodenaktivierungen (Methodenaktivierungen in normaler Flussrichtung) vor dem Austritt einer Exception aus einer Methode.

Der Spyder-Kern übernimmt für konkrete Anwendungen hinter der fachlichen Schnittstelle weitere Dienste. Zum einen übersetzen diese Dienste elementare Operationen der CLR zurück auf die Abstraktionsebene des Testgegenstands, ohne dass eine Spyder-Anwendung etwas davon erfährt. Die Dienste betreffen die

  • Objektverwaltung, d. h. die Abbildung veränderlicher Objektadressen auf sitzungsweit eindeutige Objektkennzeichen, und die

  • Thread-Verwaltung, d. h. die Abbildung von Betriebssystem-Threads auf sitzungsweit eindeutige Thread-Kennzeichen.

Zum anderen verfeinert der Spyder-Kern den Ereignisfilter der CLR. Spyder- Anwendungen erhalten nur jene Ereignisse und Daten, die aus der Sicht des Laufzeitmodells und in Bezug auf den Testgegenstand relevant sind. Zur Bestimmung der inhaltlichen Relevanz zieht der Spyder- Kern Programmbereiche heran, z. B. bestimmte Klassen oder Assemblies. Eine Spyder-Anwendung parametrisiert den Spyder-Kern mit Programmbereichen in Form von Filterkriterien, die auf Testgegenstand und Test zugeschnitten sind.

3) Ein Programm, das instabile Programmteile verwendet, sollte gegen deren Bewegungen durch den Einzug von Abstraktionsschichten abgefedert werden.

Testgegenstand

Spyders fachliches Modell deckt das an den Schnittstellen von Komponenten beobachtbare Verhalten ab. Es ist schwer, ein reales Programm zu finden, anhand dessen der Funktionsumfang von Spyder nicht nur gezeigt sondern auch verifiziert werden kann. Daher haben wir einen Testrahmen (ein Framework) entwickelt, der Mehrbenutzerprogramme simulieren und mit verschiedenen Testtreibern parametrisiert werden kann. Die gesamte Testsuite ist so ausgelegt, dass sie parametrisierbare und daher leicht reproduzierbare Tests ermöglicht. Auf dieser Grundlage ist unter anderem ein einfaches ADO.NET-Testprogramm entstanden mit dem Ziel, den Anwendungsbereich von Spyder unter realitätsnahen Bedingungen weitgehend abzudecken. Der Anwendungsbereich des Testprogramms ist das Verlagswesen.

Das Programm unterstützt mehrere Benutzer (d. h. mehrere Threads), die auf dem Datenbestand (Bücher und Autoren) konkurrierende Aktionen ausführen (d. h. Exceptions auslösen können) und die sowohl mit elementaren und strukturierten Werten (d. h. mit Wertetypen) als auch mit Objekten (d. h. mit Referenztypen) arbeiten.

Da Spyder primär Last- und Funktionstests unterstützt, muss das Testprogramm testbar sein. Nun können wir diese Eigenschaft nicht für reale Programme voraussetzen. Das ist aber in unserem Fall kein Problem, da wir ja nicht das Testprogramm testen wollen, sondern Spyder. Die Testbarkeit des Testprogramms in Bezug auf Spyder äußert sich darin, dass Programmkomponenten in Schichten organisiert sind und sowohl syntaktisch als auch semantisch hinreichend klar spezifizierte Schnittstellen besitzen. Das vereinfacht nicht nur die Fehlersuche im Testprogramm, sondern auch die Spezifikation von minimalen Benutzungsprofilen (Programmschnitten) mit Hilfe von Filtern und damit Tests von Spyder selbst. Abbildung 3 vermittelt einen Eindruck vom Testprogramm. Die Abbildung enthält einige Angaben zur Architektur, die nicht nur die Planung und Abwicklung von Komponententests unterstützen sollen, sondern auch die Prüfung der Plausibilität von Testergebnissen.

Schichtenarchitektur des Programms zum Testen von Spyder
Abb. 3: Schichtenarchitektur des Programms zum Testen von Spyder. Schicht S1 ist der Testrahmen. Schichten S2 bis S4 gehören zum Testprogramm. Einstellbare Teile sind grün markiert.

Spyder.NET im Einsatz

Für gewöhnlich verwenden wir Tests, um Fehler zu provozieren. Hier verwenden wir die Tests zur Illustration der Funktionalität von Spyder. Zu diesem Zweck schließen wir einen einfachen Spurverfolger (Tracer) an den Kern von Spyder an. Tabelle 3 gibt einen Überblick über vier ausgewählte Testfälle. Die Tests lassen sich wie folgt charakterisieren:

  • Testfall 1: Es wird ein Oberflächenprofil von ADO.NET für einen virtuellen Benutzer aufgezeichnet. Der Testfall repräsentiert eine typische Anwendung des Rekorders als Ausgangspunkt für den Test einer relationalen Datenbank unter Last.

  • Testfall 2: Wie Testfall 1 mit dem Unterschied, dass die aufgezeichneten Daten auf das Abstraktionsniveau des objektorientierten Programmiermodells gehoben werden und damit näher am Anwendungsbereich sind.

  • Testfall 3: Wie Testfall 2 mit dem Unterschied, dass kein Oberflächenprofil aufgezeichnet wird sondern ein Tiefenprofil ausgehend von der Objektschnittstelle (S3: ObjectInterface in Abb. 3) über die Datenschnittstelle (S5: DataInterface in Abb. 3) bis in die FCL hinein.

  • Testfall 4: Wie Testfall 3 mit dem Unterschied, dass mehrere virtuelle Benutzer konkurrierende Operationen über der Datenbank ausführen, in Konflikt geraten und Exceptions auslösen.

Konfigurationen ausgewählter Spyder-Tests mit (K)onstanten und (V)ariablen Parametern
Tabelle 3: Konfigurationen ausgewählter Spyder-Tests mit (K)onstanten und (V)ariablen Parametern, Iter. = Iterationen; weitere Begriffe wie in Abb. 3

Wir beschränken uns hier auf eine kurze Analyse der Programmläufe, die der Spurverfolger für die Testfälle 1 und 2 erzeugt (siehe Abb. 4 und Abb. 5). Programmlauf 1 ist komplex und geprägt von technischen Konstrukten aus dem ADOUmfeld, hier OLE DB (Object Linking and Embedding Database). Zu sehen ist die Einrichtung der OLE-DB-Umgebung, an der unter anderem die Datenbankverbindung (OleDbConnection), mehrere Datenadapter (OleDbDataAdapter) und Datenleser (OleDbDataReader) beteiligt sind. Auf diesem Abstraktionsniveau wird auch nach 39 Operationen (nach 78 Ereignissen in Abb. 4) noch nicht auf einen Datensatz aus dem Bücherbestand zugegriffen.

Programmlauf 2 repräsentiert denselben Anwendungsfall auf einer höheren Abstraktionsebene. Daher kann seine relative Einfachheit und Anwendungsnähe nicht überraschen. Auf diesem Abstraktionsniveau wird bereits nach drei Operationen (nach sechs Ereignissen in Abb. 5) auf den ersten Datensatz zugegriffen. Der näher an der Anwendung angesiedelte Programmlauf 2 dokumentiert zwei Threads. Das folgt aus der Fähigkeit des Testrahmens, mehrere Benutzer zu simulieren. Aus Anwendersicht lässt sich das so erklären, dass alle virtuellen Benutzer (in diesem Testfall jedoch nur einer) auf den Buchbestand eines Verlagshauses zugreifen. Der gemeinsam benutzte Teil der Infrastruktur wird in der Initialisierungsphase vor den eigentlichen Testläufen eingerichtet. Für die Generierung eines Testtreibers für die Datenbank wäre es bedeutungslos, dass ein separater Thread (der Thread des Testrahmens) die Initialisierung erledigt. Bemerkenswert ist auch, dass die in Programmlauf 2 aufgezeichneten Methoden und (benutzerdefinierten) Typen zu demselben, in sich geschlossenen Namensbereich gehören und maximal eine Stack- Ebene auseinander liegen. Programmlauf 1 hingegen überstreicht vier Namensbereiche: Neben System.Data.OleDb sind das System.Data, System.Data.ProviderBase und System.Data.Common; im Programmlauf berücksichtige Methoden werden auf zum Teil relativ weit auseinander liegenden Stack-Ebenen aktiviert. Beide Programmläufe zeigen Verwendungszusammenhänge anhand von Datenflüssen.

Programmlauf 1 für Testfall 1
Abb. 4: Programmlauf 1 für Testfall 1 (Tab. 3). Bedeutung der Kopfzeile: Ereignis Nummer, Thread-Kennzeichen, Richtung des Steuerflusses, Stack-Ebene, Objektkennzeichen, Resultat, Namensbereich (hier: Klassen oder Schnittstellen), Methode, Parameter. Gleichfarbige Objektkennzeichen zeigen Datenflüsse.

Programmlauf 2 für Testfall 2
Abb. 5: Programmlauf 2 für Testfall 2 (Tab. 3). Die Kopfzeile hat die gleiche Bedeutung wie in Abb. 4.

Welche Schlussfolgerungen können wir aus den besprochenen Testfällen ziehen? 4) Ein auf der Basis von Programmlauf 1 generiertes Gerüst für einen Testtreiber ist sicher schwerer anzupassen, als ein auf der Basis von Programmlauf 2 generiertes Gerüst. Die Komplexität von Programmlauf 1 legt die Suche nach einem besser geeigneten Programmschnitt nahe. Auch eine Verbesserung der Testbarkeit des Testgegenstands käme in Frage, sofern dies möglich ist. Testtreiber, die an einer klar definierten Schnittstelle auf einer höheren Abstraktionsebene ansetzen und daher vom Testgegenstand weiter entfernt sind, kosten freilich mehr Ressourcen und gestatten meist keine punktgenauen Tests. Unter dem Strich entschärft jedoch ein Rekorder von Benutzungsprofilen für Komponenten á la Spyder.NET die Entscheidung für oder gegen einen der Testansätze und unterstützt ein "sowohlals- auch", da die Aufwände für die Durchführung von Komponententests sinken und in die Planung, Auswertung oder auch Ausweitung von Komponententests verschoben werden können.

4) Aus dem hier nicht weiter besprochenen Testfall 3 schließen wir, dass nicht okumentierte Methoden mit gefährlich klingenden Namen zuverlässig arbeiten können. Das betrifft die Methoden "DangerousGetAccessorHandle" und "DangerousGetDataPtr" der Klasse "System.Data.OleDb.RowBinding".

Schlussbetrachtung

Die CLR bietet Werkzeugen über die Profiler-Schnittstelle einen reichen Fundus an Daten über das Verhalten von Komponenten an. Rekorder können diese Daten für die Aufzeichnung von Anwendungsfällen als Oberflächenprofile oder Tiefenprofile (Durchstiche) nutzen, z. B. in Form von Log-Dateien oder Gerüsten für Testtreiber, und damit die Suche von Fehlern und Fehlerursachen im Zusammenspiel von Komponenten in unterschiedlichen Einsatzszenarien beschleunigen. Spyder.NET ist ein Rekorder, der ein einheitliches Laufzeitmodell anbietet und mit verschiedenen, näher an der Anwendung operierenden Rekorderteilen parametrisiert werden kann, beispielsweise mit Spurverfolgern oder Testtreiber-Generatoren. Spyder.NET greift aus den zahlreichen Daten, die die CLR über Programmläufe auf dem Niveau der CLR anbietet, modellrelevante heraus, filtert sie nach ihrem Bezug auf die jeweiligen Testgegenstände (Komponenten) und strukturiert die verbleibenden Daten modellkonform. Damit leistet Spyder.NET in enger Kooperation mit der CLR auf der einen Seite und anwendungsnahen Rekorderteilen auf der anderen Seite einen Beitrag zur Verbesserung der Qualität komponentenbasierter Programme.

Literatur & Links

[Bea04] B. Beauchemin, Tracing Data Access: A Gentle Introduction to ADO.NET 2.0 Trace Facilities, 2004, (siehe msdn.microsoft.com/library/en-us/dnadonet/html/tracingdataaccess.asp, abgerufen am 8.6.2005)

[Coa03] T. Coatta, Another Day another Bug, in: ACM Queue, Vol. 1, No. 6, 2003

[Frö01] J.H. Fröhlich, H. Vogel, Ein Service zum Testen von COM+-Komponenten unter Last, in: OBJEKTspektrum 3/2001

[Gat04] K.S. Gatlin, Trials and Tribulations of Debugging Concurrency, in: ACM Queue, Vol. 2, No. 7, 2004

[Hil05] J. Hilyard, No Code Can Hide from the Profiling API in the .NET Framework 2.0, in: MSDN Magazine, Vol. 20, No. 1, 2005

[Mil04] J.S. Miller, The Common Language Infrastructure Annotated Standard, Addison- Wesley, 2004

[Rob03] J. Robbins, Debugging Applications for Microsoft .Net and Microsoft Windows, Microsoft Press, 2003

Die Autoren

Joachim H. Fröhlich

Joachim H. Fröhlich

ist Assistent am Institut für Wirtschaftsinformatik- Software Engineering der Johannes Kepler Universität Linz. Er spezifiziert, testet und entwirft Spyder.NET. E-Mail: joachim.froehlich@acm.org

Reinhard Wolfinger

Reinhard Wolfinger

entwirft, implementiert und testet Spyder.NET im Rahmen seiner Diplomarbeit. E-Mail: reinhard.wolfinger@swe.uni-linz.ac.at


© 2012 Microsoft. Alle Rechte vorbehalten. Nutzungsbedingungen | Markenzeichen | Informationen zur Datensicherheit
Page view tracker