Agile Modellierung mit
UML
Loading

7.6 Zusammenfassung und offene Punkte beim Testen

Zusammenfassung

Qualitätssicherung ist ohne systematisches und diszipliniertes Entwickeln von Tests nicht möglich. Die Definition von Testfällen ist aber aufwändig, insbesondere da für geschäftskritische Systeme eine gute Überdeckung durch Testfälle notwendig ist. Die effiziente Entwicklung und Darstellung von Testfällen spielt daher eine wesentliche Rolle. Die Techniken der UML/P erlauben eine kompakte und übersichtliche Darstellung von Tests durch die Kombination verschiedener Diagramme zur Darstellung der Testdaten, des Testtreibers und des Sollergebnisses sowie von Invarianten des Systems. Diese Diagramme und Spezifikationen können unabhängig voneinander entwickelt werden, sind kompakter und leichter verständlich als Testcode und erleichtern daher die Wiederverwendung.

In einer Test-First-Vorgehensweise können mittels Sequenzdiagrammen zunächst Verhaltensmuster spezifiziert werden, die gegebenenfalls mit Ergänzungen als Testfallbeschreibungen einsetzbar sind, bevor eine Implementierung vorgenommen wird. Damit ist dieser Ansatz eine Erweiterung des klassischen Vorgehens Sequenzdiagramme bei der frühen Anforderungserhebung einzusetzen.

Nach einer Implementierung ist die Definition weiterer Testfälle sinnvoll, um Rand- und Sonderfälle zu prüfen. Wie intensiv getestet wird, hängt von der gewünschten Qualität und der zugrunde liegenden Methodik ab. In einem agilen Ansatz werden vor allem an besonders kritischen und komplexen Stellen Metriken zur Analyse der Testqualität eingesetzt und sonst auf die Erfahrung der Testentwickler vertraut.

Ein UML/P-Modell kann Gegenstand des Tests sein, wenn das Modell konstruktiv zur Beschreibung des Systems verwendet wird. Ein Modell kann aber auch als testbare Spezifikation eingesetzt werden, wenn bereits eine Implementierung gegeben ist. Wie bereits die Verfahren zur Codegenerierung in Kapitel 4 und Kapitel 5 gezeigt haben, können einzelne Konzepte des Modells auch unterschiedliche Rollen übernehmen und je nach Ansatz der Generierung konstruktiv oder als Testcode eingesetzt werden.

Nach einer Einführung in die Begriffswelt der Testverfahren und einer kurzen Beschreibung zweier wesentlicher Testwerkzeuge wurde in diesem Kapitel demonstriert, wie UML/P-Diagramme zur Modellierung von Tests eingesetzt werden.

Trotz vorhandener Literatur zum Thema Testen von Software gibt es gerade beim Konformitätstest von Code, der aus graphisch notierten Implementierungsmodellen generiert wurde, eine Reihe von Verbesserungsmöglichkeiten und offenen Enden, von denen einige nachfolgend diskutiert werden. In diesem Buch sind außerdem Lasttests, Qualitätssicherungsmaßnahmen wie Inspektionen und Modellreviews oder die Vorgehensweisen für interaktive Tests unter Nutzerpartizipation zum Zweck der Abnahme nicht behandelt.

Entwicklungspotential bei der Testfallgenerierung

Die Anwendung von UML/P-Diagrammen und insbesondere OCL-Spezifikationen zur Generierung automatisierter Testfälle bietet noch großes Entwicklungspotential. Wie in Abschnitt 7.3 beschrieben, wäre eine möglichst effektive Generierung einer den Code ausreichend überdeckenden, aber möglichst kleinen Menge von Testfällen aus einer OCL-Methodenspezifikation hilfreich, um in noch effizienterer Form Testfälle zu entwickeln. Das gleiche gilt für die in Abschnitt 7.5 diskutierten Statecharts.

Im Kontext der UML und der OCL sind noch wenig Arbeiten zum Thema agiles Testen bekannt. Das liegt teilweise daran, dass lange davon ausgegangen wurde, dass die Testverfahren, die im Wesentlichen alle bereits für prozedurale Programmierung entwickelt wurden [Bei04Lig90], nur auf Objektorientierung zu übertragen wären. Durch die Vererbung und die dynamische Bindung von Methoden entstehen aber grundsätzlich neue Problemstellungen.

Als weitere Problemquelle wird die Entkopplung der Sprache UML und der vielen, teilweise sehr unterschiedlichen Vorgehensmodelle auf Basis der UML genannt [BL02], die sehr unterschiedlich detaillierte und damit unterschiedlich testbare Modelle einsetzen.

Derzeit ist dennoch eine steigende Anzahl von Arbeiten zu erkennen, die verschiedene Testverfahren auf die UML übertragen, ohne allerdings das Potential für Code- und Testgenerierung bereits auszuschöpfen. [PJH+01] beschäftigen sich etwa mit der Übertragung der aus TTCN [ISO92] bekannten Verwendung von Sequenzdiagrammen zur Testfallmodellierung. [BL02] beschreiben die systematische und teilweise automatisierte Entwicklung von Testfällen aus UML-Analysedokumenten wie Use Case Diagrammen, Sequenz-, Kommunikations- und das Domänenmodell darstellenden Klassendiagrammen. In [BB00] wird ein ähnlicher Ansatz verfolgt, der ebenfalls eine Form von Sequenzdiagrammen zur Beschreibung von Interaktionen zwischen Objekten verwendet, und dies mit einem bereits bekannten Verfahren zur Kategorie-Partitionierung [OB88] kombiniert.

In [PLP01] wird beschrieben, wie für eine für verteilte Systeme geeignete, allerdings nicht an der UML angelehnte, graphische Modellierungssprache Techniken des Constraint Reasoning angewandt werden, um aus einer abstrakten Testfallspezifikation eine Sammlung von Testfällen (dort als „Testsequenzen“) bezeichnet, zu generieren. Allerdings ist die Form der betrachteten Systeme dort statisch und deshalb die Übertragung der Algorithmen auf dynamische, objektorientierte Systeme nicht kanonisch.

Bereits in den Publikationen [DN84HT90] wurde beschrieben, dass die zufallsbasierte Generierung von Testdaten für Entwicklung von Zutrauen in die Korrektheit der Implementierung ähnlich gute Ergebnisse aufweist wie partitionsbasierte Testverfahren. Wenn diese Ergebnisse sich auch bei den heutigen Sprachen und Testverfahren bewahrheiten, dann bedeutet dies, dass die zufallsbasierte Generierung von Tests ein wesentliches Hilfsmittel für Testverfahren sein kann. Insbesondere Statecharts und OCL-Nachbedingungen sind dann als Orakel für solchermaßen generierte Tests hilfreich. Ist es erwünscht, die generierten Tests zu speichern, so können dafür Objekt- und Sequenzdiagramme verwendet werden. Wie bereits in [HT90] vermerkt, muss bei den Testverfahren eine weitere Verlagerung von der arbeitsaufwändigen manuellen Erstellung zur automatisierten Testgenerierung stattfinden.

Dieses Kapitel konnte nur einen kleinen Teil der insgesamt zum Thema Testen existierenden Konzepte, Techniken und Vorgehensweisen diskutieren. An verschiedenen Stellen wurde deshalb auf entsprechende Literatur verwiesen. Generell ist festzustellen, dass für eine effektive Unterstützung der Testfallentwicklung nicht nur für die UML gute und parametrisierte Generatoren existieren müssen, sondern auch Unterstützung für Testmuster sowie für einzubindende Frameworks und Komponenten anzubieten sind.

In objektorientierten und insbesondere agilen Vorgehensmodellen ist der Trend zu beobachten, dass das Produktionssystem explizit so strukturiert wird, dass es gut testbar ist. Dadurch ist es nicht mehr notwendig eine Testsequenz zu definieren, die ausgehend von einem Initialzustand eine bestimmte Transition, Anweisung oder Methode prüft. Stattdessen kann eine Objektstruktur angegeben werden, die direkt zu dessen Prüfung führt. Objektstrukturen sind viel leichter zu finden, der Test wird effektiver in der Ausführung und besser verständlich.

Symbolisches Testen

Ein Beispiel für die potentielle Weiterentwicklung der Testverfahren basiert auf der Interpretation von symbolischen statt echten Werten. So kann die Verwendung von abstrakten, durch Symbole repräsentierten Elementen in Objektdiagrammen, wie in Abschnitt 4.2.2, Band 1 diskutiert, als Grundlage für eine interessante Erweiterung des Einsatzes dieser Diagramme dienen, die hier als Ausblick skizziert wird. Durch die Nutzung symbolischer Werte können Testfälle verallgemeinert werden. Dabei wird in einer Vorbedingung ein symbolischer Wert angegeben, der innerhalb der getesteten Methode verändert oder für die Berechnung anderer Attribute genutzt wird. Dabei wird der Ausdruck nicht ausgewertet, sondern unausgewertet als Term abgelegt. In der Nachbedingung kann dann statt eines konkreten Werts geprüft werden, ob der zur Berechnung verwendete Term der gewünschten Intention entspricht. Die einfachste Form des Vergleichs ist die syntaktische Gleichheit, jedoch kann durch geeignete algebraische Umformung auch ein semantisch äquivalenter Term angegeben werden. Ein Beispiel ist die folgende Berechnung, die auf umständliche Weise das Maximum beider Argumente ergibt:

       Java      
  int foo(x,y) {
  int a = x;
  int b = y;
  a = a-b;
  if(x < y) b = b-a;
  return a+b;
}

Dabei wird statt mit konkreten Werten mit den symbolischen Werten x und y gerechnet. Unter der zusätzlichen Annahme x<y wird als Testergebnis für den Funktionswert y vorgegeben. Die symbolische Interpretation lässt sich durch folgende Annotation beschreiben:

       Java   
 int foo(x,y) {                // Wert   a=  b=
  int a = x;                // x
  int b = y;                // x y
  a = a-b;                 // x-y y
  if(x < y) b = b-a;        // x-y y-(x-y)
  return a+b;               // Ergebnis: (x-y)+(y-(x-y))
}

Tatsächlich ist das Ergebnis (x-y)+(y-(x-y))äquivalent zu y. In analoger Form kann die Negation der Bedingung !(x<y) symbolisch „getestet“ werden. Bei dieser Form der Interpretation erfolgt die Berechnung zumindest teilweise auf Basis symbolischer Werte. Damit entsteht eine Verallgemeinerung von exemplarischen Testfällen auf Äquivalenzklassen von Testfällen. Im obigen Beispiel sind dies nur zwei Äquivalenzklassen, beschrieben durch x<y und !(x<y), so dass dadurch eine vollständige Testüberdeckung möglich wird. Deshalb kann symbolisches Testen ein Mittel sein, eine endliche Menge verallgemeinerter Tests zu entwerfen, die eine Überdeckung der gesamten Systemfunktionalität darstellen. Damit entsteht eine Brücke zwischen exemplarischen Testverfahren und allgemeinen Verifikationstechniken [Lig90]. Allerdings sind solche Verfahren gerade im objektorientierten und von Seiteneffekten behafteten Programmierstil aufwändig. Bereits die Verwendung von Schleifen macht die endliche Äquivalenzklassenbildung zunichte, so dass die Testüberdeckung in Bezug auf der Anzahl durchlaufener Schleifen unvollständig bleibt. Heute werden unausgewertete Datenstrukturen noch kaum beim Testen, sondern vor allem in Implementierungen für Logikprogrammiersprachen wie Prolog [Llo87], funktionalen Programmiersprachen mit „lazy“-Auswertung wie Gofer [Jon96], mathematisch algebraischen Systemen wie Maple [Viv01] oder Mathematica [Wol99], Termersetzungssystemen wie OBJ [GWM+92] oder Verifikationssystemen mit Termersetzung [NPW02Pau94] erfolgreich eingesetzt. Sie werden dort aber auch für wesentlich komplexere Aufgaben wie der Verifikation eingesetzt.

Statistische, anwendungsbezogene Tests

Eine besondere Form der Testentwicklung ist beispielsweise im Cleanroom-Ansatz [PTLP98] zur Softwareentwicklung zu finden. Dort werden statistische Markov-Modelle verwendet, um die Wahrscheinlichkeiten für Anwendungsfälle zu beschreiben. Da Cleanroom primär mit Ein- und Ausgabesequenzen arbeitet, die durch Zustands-Transitions-Modelle beschrieben werden, werden durch das statistische Modell die potentiell vorkommenden Eingabesequenzen nach Wahrscheinlichkeiten gewichtet. Dadurch wird es möglich, die wahrscheinlichsten Anwendungen verstärkt zu testen. Im Kontext der UML kann dieses Verfahren auf Statecharts angewendet werden. Dazu werden auch hier die Transitionen gemäß der erwarteten auftretenden Häufigkeit gewichtet. Das heißt, es wird ein Modell der getesteten Klasse entwickelt, das beschreibt, welche Methodenaufrufe und Nachrichten mit welcher Wahrscheinlichkeit ankommen. Auf dieser Gewichtung basiert dann die Auswahl der Testpfade für das Statechart.

Alternativ, allerdings in Cleanroom selbst nicht propagiert, wäre auch die Gewichtung der Zustände nach der erwarteten Häufigkeit des Auftretens von Interesse. Außerdem lässt sich dasselbe Verfahren möglicherweise auch bei Klassendiagrammen gewinnbringend einsetzen, um eine Gewichtung der Objektstrukturen zu erreichen, die als Testdatensätze einzusetzen sind.

Die Verwendung statistischer Verfahren erlaubt in Cleanroom die Vorhersage von durchschnittlichen Fehlerhäufigkeiten in Abhängigkeit der Anzahl durchgeführter Tests und der dabei gefundenen Fehler. Insbesondere kann so ein präzises Kriterium für das Testende in Abhängigkeit der gewünschten Qualität (Fehlerhäufigkeit) entwickelt werden. Eine der wesentlichen Grundvoraussetzungen dafür ist allerdings die Erhebung einer aussagekräftigen Menge an Daten, wie gut die so entwickelten Tests tatsächlich Fehler entdecken. Solche Daten hängen von der verwendeten Programmiersprache ab und können nur in geeigneten Feldversuchen erhoben werden.


Bernhard Rumpe. Agile Modellierung mit UML. Springer 2012