Äquivalenzklassenanalyse (angelehnt an [Kle09])

Kurzform

Ein Testfall besteht aus der Spezifikation der Eingaben, der Umgebung, z. B. dem Zustand des betrachteten Objekts und dem erwarteten Ergebnis. Das generelle Ziel dabei ist, mit möglichst wenigen Testfällen möglichst viele Fehlerquellen zu überprüfen. Offen bleibt dabei die Frage nach der sinnvollen Auswahl von Testfällen. Ab der Unit-Ebene, also für einzelne Methoden, aber auch für mögliche Abläufe in Aktivitätsdiagrammen stellt die Äquivalenzklassenmethode [Lig02] einen mächtigen Ansatz dar. Äquivalenzklassen sind aus der Mathematik bekannt, sie teilen eine Menge in unterschiedliche Teilmengen auf, wobei die Zugehörigkeit zu einer Teilmenge von dem Verhalten der einzelnen Elemente bezüglich einer ausgewählten Funktionalität abhängt. Die Idee der Äquivalenzklassenbildung kann auf die möglichen Eingaben für eine Methode oder generell eine angebotene Funktionalität übertragen werden. Dabei gilt es, aus der Anforderungsbeschreibung möglichst präzise Fälle abzuleiten, die unterschiedlich bearbeitet werden sollen.

Prinzipielle Idee

Ein erstes Beispiel soll eine Methode sein, die für alle ganzen Zahlen zwischen 0 und 100 ein sinnvolles Ergebnis liefern soll. Zunächst ist immer zu klären, was der informelle Ausdruck "zwischen" bedeutet, ob also die mathematisch exakte Beschreibung der erlaubten Werte bei eins oder null anfängt und bei 99 oder 100 endet. Ist dies geklärt, hat man z. B. eine Äquivalenzklasse mit gültigen Werten 0<x<100. Da für den Parameter beliebige int-Werte erlaubt sind, sind alle anderen Werte ungültig und sollen z. B. nach der Anforderungsspezifikation zu Ausnahmen führen. Man könnte jetzt eine Klasse mit ungültigen Werten definieren, gerade bei Zahlenwerten ist es aber üblich, zwei Äquivalenzklassen mit ungültigen Werten, einmal mit zu kleinen Werten "x<1" und einmal mit zu großen Werten "x>99", zu bilden. Das grundsätzliche Prinzip, das im Folgenden noch weiter verfeinert wird, ist dann, dass zu jeder Äquivalenzklasse ein Testfall geschrieben wird, es hier z. B. drei Testfälle mit den Werten -2, 7 und 101 gibt.
Die Bildung von Äquivalenzklassen ist bei Nichtzahlenwerten meist etwas komplizierter, wobei für Aufzählungen z. B. mit den erlaubten Werten ROT, GELB und GRUEN einfach jeder einzelne Wert eine Äquivalenzklasse darstellt. Falls kein anderer Wert eingegeben werden kann, wie es z. B. bei Enumeration-Typen der Fall ist, gibt es keine Äquivalenzklasse mit ungültigen Werten. Handelt es sich nicht um eine Enumeration, sondern z. B. um ein Textfeld, in das ein Nutzer beliebige Zeichen eingeben kann, gibt es eine weitere Klasse mit allen ungültigen Eingaben.

Konkretes Beispiel

Als Beispiel dient eine Methode, genauer ein Konstruktor, zur Verwaltung von Studierendendaten, der ein Name, ein Geburtsjahr und ein Fachbereich übergeben werden. Dabei darf das Namensfeld nicht leer sein, das Geburtsjahr muss zwischen 1900 und 2000 liegen und es können nur die Fachbereiche FBING, FBBWL und FBPOL aus einer Aufzählung übergeben werden. Insgesamt gibt es dann folgende Äquivalenzklassen für die Eingabewerte. Diese Äquivalenzklassen sind zu testen. Da man aber bei einem Methodenaufruf mehrere Parameter zusammen übergeben kann, kann man die Anzahl der Tests verringern. Dabei gilt, dass man beliebig viele gültige Werte aus Äquivalenzklassen zu einem Test kombinieren kann, so kann ein Test gleich die Äquivalenzklassen Ä1, Ä4 und Ä6 abdecken. Bei ungültigen Äquivalenzklassen muss man vorsichtiger sein. Hier gilt die Regel, dass ein Wert aus einer ungültigen Äquivalenzklasse nur zusammen mit gültigen Werten anderer Klassen geprüft werden darf. Man stellt so sicher, dass dieser eine ungültige Wert die Problembehandlung auslöst. Dieser Testfall erlaubt damit auch keine Aussagen über die genutzten gültigen Äquivalenzklassen.

Testnummer 1 2 3 4 5 6
geprüfte Klasse Ä1 Ä4 Ä6 (Ä1) (Ä4) Ä7 (Ä1) (Ä4) Ä8 Ä2 Ä3 Ä5
Name "Meier" "Schmidt" "Schulz" "" "Meier" "Meier"
Geburtsjahr 1987 1989 1985 1988 1892 2006
Fachbereich FBING FBBWL FBPOL FBING FBING FBING
Ergebnis ok ok ok Abbruch Abbruch Abbruch

Man kann dann die in der Tabelle beschriebenen Testfälle konstruieren. Sind Klassen in Klammern genannt, bedeutet dies, dass sie bereits mit einem anderen Testfall abgedeckt werden.

Grenzwertanalyse

Kritische Leser haben sicherlich darauf gewartet, dass man die Grenzen von den Äquivalenzklassen genauer betrachtet, da jeder erfahrene Programmierer weiß, dass hier häufiger Fehler eingebaut werden. So ist ein Fehler für das Geburtsjahr 1986 unwahrscheinlich, eher kann es passieren, dass das Geburtsjahr 2000 als ungültig angenommen wird. Aus diesem Grund wird bei der Testfallerstellung zusätzlich die Grenzwertanalyse genutzt, d. h., dass für jede Äquivalenzklasse, wenn möglich, genau die Grenzen getestet werden, an denen der Übergang zu einer anderen Klasse stattfindet. Dies ist bei Zahlenbereichen sehr gut möglich, da es hier zumeist eine obere oder eine untere Schranke gibt. Die Äquivalenzklasse Ä3 hat die obere Grenze 1899, die Äquivalenzklasse Ä5 die untere Grenze 2001, die Äquivalenzklasse Ä4 die untere Grenze 1900 und die obere Grenze 2000.
Bei der Zahlendarstellung im Computer ist zu beachten, dass es kleinste und größte Zahlen bei den zugehörigen Datentypen gibt. Können diese Grenzen eine Rolle spielen, muss man sie bei der Grenzwertanalyse berücksichtigen. Dies ist im vorgestellten Beispiel nicht der Fall. Bei Fließkommazahlen ist gegebenenfalls noch zu beachten, dass es zwischen der Zahl Null und der kleinsten positiven bzw. negativen Zahl immer eine kleine Lücke gibt, so dass man gegebenenfalls mit diesen kleinsten Zahlen ungleich Null auch testen sollte. Da alle Grenzwerte immer zu einer Äquivalenzklasse gehören, kann man dies bei der Erstellung der Testfälle berücksichtigen.

Testnummer 1 2 3 4 5 6
geprüfte Klasse Ä1 Ä4U Ä6 (Ä1) Ä4O Ä7 (Ä1) (Ä4) Ä8 Ä2 Ä3O Ä5U
Name "Meier" "Schmidt" "Schulz" "" "Meier" "Meier"
Geburtsjahr 1900 2000 1985 1988 1899 2001
Fachbereich FBING FBBWL FBPOL FBING FBING FBING
Ergebnis ok ok ok Abbruch Abbruch Abbruch

In der vorherigen Tabelle sind die Testfälle unter Berücksichtigung der Grenzen angegeben, dabei steht ein "U" für eine berücksichtigte untere und ein "O" für eine berücksichtigte obere Grenze. Durch die Grenzwertanalyse kann es passieren, dass sich die Anzahl der benötigten Testfälle um zwei zu testende Grenzen erhöht.
Jeder der gefundenen Testfälle kann dann in einen getrennten Unit-Testfall umgesetzt werden, wie es hier beispielhaft für die Fälle 1 und 4 beschrieben ist.

 
public void test1(){
  try{
    new Immatrikulation("Meier",1900,Bereich.FBING);
  }catch(ImmatrikulationsException e){
    fail("falsche Exception");
  }
}

public void test4(){
  try{
    new Immatrikulation("",1988,Bereich.FBING);
    fail("fehlende Exception");
  }catch(ImmatrikulationsException e){
  }
}
 
Mit etwas formaler mathematischer Bildung könnte man fordern, dass jede Äquivalenzklasse mit jeder anderen zusammen geprüft wird, was hier grob zu 2*3*3=18 Testfällen führen würde. Da man aber bei der Wahl der Klassen davon ausgeht, dass sich diese nicht gegenseitig beeinflussen und die Anzahl der Testfälle sonst auch sehr schnell enorm wachsen würde, verzichtet man ab einer projektindividuell festzulegenden Anzahl auf diesen Ansatz.

Abhängige Eingabeparameter

Was passiert aber, wenn das Ergebnis doch von der Kombination der Eingabewerte abhängig ist? In diesem Fall sollte man Äquivalenzklassen als Kombinationen der Eingabewerte bilden. Dies kann allerdings leicht zu sehr komplexen Strukturen führen, so dass man als Kompromiss dann wieder zu der ursprünglich getrennten Betrachtung der Eingabeparameter zurückkehrt. Die komplexere Betrachtung von sich gegenseitig beeinflussenden Eingabeparametern wird jetzt mit einem Beispiel verdeutlicht. Es soll eine Methode geschrieben werden, die das Maximum aus den drei übergebenen Zahlen berechnet. Die fehlerhafte Beispielimplementierung sieht wie folgt aus.

public class Maxi {
  public static int max(int x, int y, int z){
    int max=0;
    if(x>z)max=x;
    if(y>x)max=y;
    if(z>y)max=z;
    return max;
  }
}

Benutzt man den einfachen Äquivalenzklassenansatz, so kann jeder Parameter beliebige Werte annehmen, so dass es für jeden Parameter genau eine Äquivalenzklasse gibt, die alle int-Werte enthält. Es würde dann reichen, einen Testfall zu schreiben, der z. B. die Werte x=7, y=5 und z=4 umfasst. Die Programmausführung liefert das erwartete Ergebnis 7.
Generell muss man jetzt alle Varianten von Abhängigkeiten der Eingabeparameter berücksichtigen, was sehr schnell zu einer Explosion der Anzahl der Testfälle führt, so dass man häufig nur eine Teilmenge auswählt.
Im konkreten Beispiel sind folgende 13 Fälle zu beachten: x>y=z, y=z>x, y>x=z, x=z>y, z>y=x, y=x>z, z>y>x, z>x>y, y>z>x, y>x>z, x>z>y, x>y>z, x=y=z
Bei der Ausführung der zugehörigen Testfälle scheitern mehrere dieser Fälle.
Dieses kleine Beispiel deutet bereits an, dass die systematische Ableitung von Testfällen, die möglichst alle Probleme aufdecken, eine komplexe und teilweise sehr aufwändige Aufgabe ist. Tests zeigen nur die Abwesenheit von Fehlern in konkreten Situationen, nämlich genau den getesteten Fällen. Eine Garantie der Korrektheit kann so nicht erreicht werden, weshalb in sehr kritischen Fällen über den Einsatz formaler Ansätze nachgedacht werden muss.

Objektorientierung

Die bisher gezeigten Fälle berücksichtigen die Objektorientierung nicht, da der Zustand eines Objekts bei den bisherigen Betrachtungen eines Konstruktors und einer Klassenmethode keine Rolle spielten. Setzt man den Äquivalenzklassenansatz konsequent für Objekte um, dann ist der Objektzustand ein weiterer Eingabeparameter. Man muss dazu untersuchen, welche unterschiedlichen Objektzustände das Berechnungsergebnis beeinflussen können. Damit werden Äquivalenzklassen für alle möglichen Objektzustände gebildet, die dann bei der Testfallerstellung berücksichtigt werden müssen. Die Komplexität der Objekt-Äquivalenzklassen hängt unter anderem stark von der Anzahl der Exemplarvariablen ab.

Zurück zur Methodenübersicht
Zurück zur CSI-Hauptseite