mutation testing mit refacola - fernuni-hagen.de · februar 2012 markus grothoff . inhalt ... wie...

74
Fernuniversität in Hagen Fakultät für Mathematik und Informatik Lehrgebiet Programmiersysteme Prof. Dr. Friedrich Steimann Mutation Testing mit Refacola Abschlussarbeit im Studiengang Bachelor of Science in Informatik Betreuer: Dipl.-Inform. Andreas Thies Februar 2012 Markus Grothoff Matrikelnummer: 7593058 E-Mail: [email protected]

Upload: others

Post on 06-Aug-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

Fernuniversität in Hagen Fakultät für Mathematik und Informatik Lehrgebiet Programmiersysteme Prof. Dr. Friedrich Steimann

Mutation Testing mit Refacola Abschlussarbeit im Studiengang Bachelor of Science in Informatik

Betreuer: Dipl.-Inform. Andreas Thies Februar 2012

Markus Grothoff Matrikelnummer: 7593058 E-Mail: [email protected]

Page 2: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung
Page 3: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

Erklärung

Hiermit erkläre ich, dass ich diese Abschlussarbeit selbstständig verfasst und noch nicht anderweitig

für Prüfungszwecke vorgelegt habe. Ich habe keine anderen als die angegebenen Quellen und Hilfs-

mittel benutzt. Wörtliche und sinngemäße Zitate wurden als solche gekennzeichnet.

Hagen, 28. Februar 2012

Markus Grothoff

Page 4: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung
Page 5: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

Inhalt

1 Einleitung ....................................................................................................................................... 1

2 Grundlagen .................................................................................................................................... 3

2.1 Unit Tests ................................................................................................................................ 3

2.2 Unit Tests und Mutation Testing ............................................................................................. 5

2.3 Constraint-basierte Refaktorisierung ....................................................................................... 8

2.4 Von der constraint-basierten Refaktorisierung zum Mutant ................................................. 11

2.5 Refacola ................................................................................................................................. 14

3 Implementierung ......................................................................................................................... 19

3.1 Anforderungen ....................................................................................................................... 20

3.2 Umsetzung ............................................................................................................................. 21

3.2.1 Anwendungskern ........................................................................................................... 23

3.2.1.1 Vorbedingungen ........................................................................................................ 24

3.2.1.2 Erzeugung der Java-Faktenbasis ............................................................................... 25

3.2.1.3 Initialisierung des Kontextes einer Refaktorisierung ................................................ 25

3.2.1.4 Generierung von Mutationen ..................................................................................... 26

3.2.1.5 Suche nach Lösungen für Mutationen ....................................................................... 27

3.2.1.6 Änderung des ursprünglichen Programms zu einem Mutanten ................................. 29

3.2.1.7 Ausführung der Testbasis und Bestimmung des Ergebnisses ................................... 30

3.2.2 JUnit Test Runner .......................................................................................................... 31

3.2.3 Benutzungsoberfläche ................................................................................................... 33

3.2.3.1 Aufbau und Inhalt ...................................................................................................... 34

3.2.3.2 Verwendung des Model View Presenter – ViewModel Entwurfsmusters ................ 38

3.2.3.3 Darstellungs-Bug bei Verwendung einer SWT-Tabelle ............................................ 41

3.3 Auszeichnung von Constraint-Regeln ................................................................................... 42

3.4 Beispiel zur Anwendung des Mutation Testing Frameworks ............................................... 46

3.5 Mögliche Verbesserungen und Erweiterungen...................................................................... 51

4 Zusammenfassung und Fazit ...................................................................................................... 55

5 Literaturverzeichnis .................................................................................................................... 57

A Inhalt der beiliegenden DVD ...................................................................................................... 61

B Installation und Konfiguration .................................................................................................. 63

C Benutzungsanleitung für den Refacola-Entwickler .................................................................. 65

D Benutzungsanleitung für den Entwickler .................................................................................. 67

Page 6: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung
Page 7: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

1 Einleitung 1

1 Einleitung

Die Anforderungen, die heutige Programme erfüllen müssen, sind vielfältig und führen zu einer ent-

sprechend hohen inneren Komplexität der Software. Die Entwicklung vollzieht sich evolutionär über

einen längeren Zeitraum, in welchem neue Funktionen hinzugefügt und Fehler ausgemerzt werden.

Dabei besteht immer das Risiko vorhandene Funktionalität zu brechen. Anpassungen der Code-Basis

haben direkten Einfluss auf die Struktur und die Qualität des Codes. Das Hinzufügen von Funktionali-

tät führt im Allgemeinen zu einer Reduzierung der Code-Qualität. Diesem kann durch regelmäßige,

entwicklungsbegleitende Umstrukturierung der Code-Basis in Form von Refaktorisierungen entgegen-

gewirkt werden. Auch dabei besteht die Gefahr, dass neue Fehler hinzukommen.

Im Zuge der Qualitätssicherung wird Software auf unterschiedlichen Ebenen Tests unterzogen. Je

später ein Fehler entdeckt wird, desto aufwändiger ist seine Behebung, da Abhängigkeiten zu weiteren

Komponenten bestehen können, sodass sich die Eliminierung des Fehlers auch auf andere Bereiche

der Software auswirken kann. Manuelle Tests zum Aufspüren von Fehlern können hilfreich sein, sind

aber zeitlich aufwendig durchzuführen und lassen sich nur schwierig auf wenige Teilbereiche der

Software begrenzen. Automatisierte Tests können, sofern korrekt verwendet, immer unter den glei-

chen kontrollierten Bedingungen wiederholt und häufiger als manuelle Tests durchgeführt werden. Sie

sind im Allgemeinen verlässlicher als manuelle Tests. Nichtsdestotrotz gibt es Aspekte der Software,

die nur manuell getestet werden können, wie z. B. ihre Benutzbarkeit.

Unit Tests sind der erste Schritt einer Reihe von automatisierten Tests und werden zum isolierten Tes-

ten einzelner Software-Module eingesetzt. Dazu werden vom Entwickler Testfälle ausgearbeitet, die in

Unit Tests umgesetzt werden. Jeder Testfall überprüft eine konkrete Annahme hinsichtlich der Imple-

mentierung einer Klasse oder einer Methode und ist recht feingranular, sodass sich von Unit Tests

aufgedeckte Fehler auf einen kleinen Codeabschnitt eingrenzen lassen.

Das Mutation Testing setzt genau bei den Unit Tests an. Aus dem zu testenden Programm werden

durch automatisierte Manipulationen an der Code-Basis Programme generiert, die sich potenziell an-

ders verhalten als das ursprüngliche Programm. Das veränderte Verhalten wird als fehlerhaft betrach-

tet. Diese sogenannten Mutanten werden einer Testbasis bestehend aus Unit Tests unterzogen, die nun

das geänderte Verhalten entdecken sollen. Mutanten, die von der Testbasis entdeckt werden, bestäti-

gen, dass in dem entsprechenden Fall die Testabdeckung hoch genug war, um das Fehlverhalten fest-

zustellen. Sollten Mutanten die Testbasis passieren, kann das ein Indiz dafür sein, dass die Testabde-

ckung nicht ausreichend ist, sollte der Mutant tatsächlich ein dem ursprünglichen Programm abwei-

chendes Verhalten aufzeigen. Aus den nicht entdeckten Mutanten gewonnenen Informationen können

verwendet werden, um weitere Testfälle zu entwickeln.

Die Schwierigkeit beim Mutation Testing besteht darin, einerseits nur syntaktisch und semantisch

korrekte Mutanten zu generieren. Andererseits sollen auch nur Mutanten generiert werden, die nicht

verhaltensäquivalent zum ursprünglichen Programm sind. Entscheidend ist dabei das von außen sicht-

bare Verhalten. Denn nur diese Mutanten können auch von einer Testbasis entdeckt werden. Die

constraint-basierte Refaktorisierung bietet eine interessante Basis, die, entsprechend für das Mutation

Testing angepasst, eine Lösung für diese Probleme darstellen kann. Die Generierung von verhaltens-

äquivalenten Mutanten kann sie zwar nicht verhindern, aber die Anzahl solcher für das Mutation Tes-

ting unerwünschten Mutanten wird reduziert.

Ziel dieser Arbeit ist die Erweiterung der am Lehrgebiet Programmiersysteme der Fernuniversität

Hagen entwickelten Sprache Refacola, die programmiersprachenunabhängig deklarative Spezifizie-

Page 8: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 1 Einleitung

rungen constraint-basierter Refaktorisierungen ermöglicht, um ein Framework zur Durchführung von

Mutation Testing für in Java geschriebene Programme. Dazu kann auf bereits vorhandene Komponen-

ten von Refacola aufgesetzt werden. Mit dem Mutation Testing Framework sollen aus Java-

Programmen Mutanten generiert und auf Basis einer vom Entwickler ausgewählten Testbasis ausge-

wertet werden.

Page 9: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 3

2 Grundlagen

Dieses Kapitel stellt die für diese Arbeit nötigen Grundlagen über Unit Tests, Mutation Testing und

Refacola zusammen und erläutert ihre Zusammenhänge.

Unit Tests sind nicht nur ein wesentlicher Bestandteil der agilen Software-Entwicklung sondern die-

nen auch als Basis für das Mutation Testing. Abschnitt 2.1 geht kurz auf den Zweck von Unit Tests ein

und erläutert einige ihrer Eigenschaften, die für das Mutation Testing von Bedeutung sind.

Der Zusammenhang zwischen Unit Tests und Mutation Testing wird dann in Abschnitt 2.2 hergestellt.

Es wird beschreiben, was Mutation Testing ist, und welches Ziel damit verfolgt wird. Losgelöst von

einer konkreten Implementierung werden der grundsätzliche Ablauf des Mutation Testing und mögli-

che Probleme, die sich bei der Anwendung ergeben können, dargestellt.

Abschnitt 2.3 geht auf die Technik der constraint-basierten Refaktorisierung ein und wie sie das Pro-

grammverhalten vor und nach einer Refaktorisierung mit Hilfe eines Constraint-Systems sicherstellen

kann. Es wird erläutert welche Bedeutung die aus einem Programm erzeugten Constraints haben und

welche Arten unterschieden werden können.

Wie die constraint-basierte Refaktorisierung genutzt werden kann, um während des Mutation Testing

Programme mit fehlerhaftem Verhalten zu generieren, und welche Anpassungen nötig sind, ist Teil

von Abschnitt 2.4. Es wird ebenso beschrieben, welchen Vorteil die Verwendung von Constraints

beim Mutation Testing mit sich bringt und welche Probleme des Mutation Testing damit gelöst wer-

den können.

In Abschnitt 2.5 werden einige für das Mutation Testing wichtige Bestandteile von Refacola vorge-

stellt und erläutert, warum sich Refacola als Basis für ein Mutation Testing Framework eignet. Danach

werden die Bereiche für die Sprachdefinition und die Constraint-Regeln näher betrachtet.

2.1 Unit Tests

Software unterliegt im Laufe ihres Lebens zahlreichen Änderungen, sowohl während ihrer Entwick-

lung als auch während der Wartung. Es werden Funktionalitäten hinzugefügt, Fehler beseitigt und

Refaktorisierungen zur Erhöhung der Codequalität durchgeführt. Jeder Eingriff in den Code hat das

Potenzial Fehler hinzuzufügen oder vorhandene Funktionalität zu zerstören. Um dem entgegenzuwir-

ken wird Software verschiedenen Tests unterzogen. Einen ersten Schritt stellen dabei die Unit Tests

dar, da sie einzelne Softwaremodule – in objektorientierten Programmiersprachen sind dies die Klas-

sen – isoliert von anderen Softwaremodulen auf ihre Korrektheit testen.

Unit Tests stellen die kleinste Testeinheit dar und werden von Entwicklern geschrieben, ausgeführt

und gepflegt. Sie zählen zu der Klasse der automatisierten Tests. Unit Tests prüfen, ob das Verhalten

von Methoden und Klassen ihren Spezifikationen entsprechen. Üblicherweise folgt ein Unit Test dem

Schema: Arrange, Act, Assert. Zuerst wird die zu testende Klasse vorbereitet und für den Testfall kon-

figuriert. Abhängigkeiten zu anderen Klassen werden durch Stubs und Mocks aufgelöst. Ist dies nicht

möglich, kann das ein Hinweis dafür sein, dass Klassen zu eng gekoppelt sind. Danach findet eine

Interaktion mit der zu testenden Klasse statt, dessen Ergebnis mit der im Testfall formulierten Annah-

me verglichen werden soll. Das Ergebnis kann in einfachen Fällen der Rückgabewert einer Methode

Page 10: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

4 2 Grundlagen

oder der Zustand der Klasse sein. Im letzten Schritt findet ein Abgleich zwischen dem Ergebnis und

der Annahme statt. Unit Testing Frameworks wie JUnit 4 unterstützen den Entwickler bei dem Schrei-

ben und Ausführen von Unit Tests und teilen ihm die Ergebnisse der Testdurchläufe mit.

Listing 1 zeigt einen einfachen mit JUnit 4 geschriebenen Unit Test, der prüft, ob toString() aufge-

rufen auf einem Exemplar von Object einen String zurückgibt, der die Zeichenfolge "Object"

enthält. Die Variable cut (Class under Test) referenziert ein Objekt der zu testenden Klasse. Da die

Standardimplementierung von toString() in Object die Konkatenation aus dem Klassennamen,

einem @-Zeichen sowie dem Hashcode des Objekts zurückgibt, trifft die im Test formulierte Annah-

me zu. Der Unit Test ist damit erfolgreich.

public class ObjectTest {

@Test

public void toString_newInstance_containsObjectString() {

Object cut = new Object();

String returnedString = cut.toString();

boolean containsObjectString = returnedString.contains("Object");

assertTrue(containsObjectString);

}

}

Listing 1 Einfacher Unit Test

Gute Unit Tests weisen eine Reihe von Eigenschaften auf, von denen die folgende Auswahl für das

Mutation Testing wesentlich von Bedeutung ist.

Automatisiert

Unit Tests können auf Knopfdruck gestartet werden. Manuelle Eingaben oder Konfigurationen sind

nicht erforderlich. Dadurch kann jeder Entwickler eines Programms zu jedem Zeitpunkt die Unit

Tests ausführen und prüfen, ob die in den Testfällen formulierten Annahmen bezüglich des Pro-

grammverhaltens weiterhin korrekt sind. Der Automatismus eliminiert Fehlerquellen, die durch

fehlerhafte, manuelle Konfigurationen entstehen können. Je einfacher Unit Tests auszuführen sind,

desto häufig werden sie in der Regel von Entwicklern durchgeführt. Fehler können so frühzeitig

erkannt werden.

Wiederholbar

Unit Tests sollen das Ergebnis einer Methode oder das Verhalten einer Klasse überprüfen und Feh-

ler aufdecken. Sollte ein Unit Test fehlschlagen, wird dieser nach Anpassung des Quellcodes vom

Entwickler erneut ausgeführt, um zu prüfen, ob der Fehler tatsächlich beseitigt wurde. Im Laufe der

Entwicklung wird ein Unit Test vielfach aufgerufen. Damit sich der Entwickler auf das Ergebnis

verlassen kann, muss sich der Unit Test ausgehend von einer bestimmten Eingabe, die im Testfall

hinterlegt ist, immer gleich verhalten. Insbesondere ist jeder Unit Test isoliert zu betrachten und

sollte nicht von anderen abhängig sein. Sie dürfen sich nicht gegenseitig beeinflussen.

Page 11: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 5

Schnell

Unit Tests müssen sehr häufig ausgeführt werden. Nach jeder Codeänderung soll nämlich geprüft

werden, ob die Tests (weiterhin) erfolgreich sind. Je länger die Ausführung der Unit Tests dauert,

desto weniger häufig wird der Entwickler sie auch laufen lassen. Fehler, die von ihnen entdeckt

werden, fallen dann erst später auf, wenn bereits mehrere Änderungen am Code vorgenommen

wurden.

2.2 Unit Tests und Mutation Testing

Mutation Testing ist eine Testmethodik, bei der Programme so manipuliert werden, dass sie ein nach

außen hin fehlerhaftes Verhalten aufzeigen. Ausgehend von einem syntaktisch und semantisch korrek-

ten Programm werden automatisiert Änderungen an der Codebasis des Programms vorgenommen. Die

manipulierten Programme werden anschließend gegen eine Testbasis ausgeführt, welche das fehlerhaf-

te Verhalten erkennen soll. Auf diese Weise kann die Testabdeckung geprüft und, falls fehlerhafte

Programme nicht entdeckt wurden, weitere Testdaten gewonnen werden, aus denen dann neue Tests

entwickelt werden können. Die durch Manipulation des ursprünglichen Programms entstehenden Pro-

gramme beim Mutation Testing werden als Mutanten bezeichnet.

Ursprünglich wurde das Mutation Testing für imperative Programmiersprachen verwendet. Durch

Austausch von Operatoren oder der Manipulation von Prädikaten konnte das Verhalten des Pro-

gramms verändert werden. Die Änderung einer Bedingung, z. B. wenn ein und-Operator durch einen

oder-Operator ersetzt wird, kann den Anwendungsfluss verändern. Objektorientierte Programmier-

sprachen bieten durch ihre Komplexität und Strukturierung dank Polymorphie und Vererbung darüber

hinaus weitere Möglichkeiten das Programmverhalten zu verändern. Hier ist insbesondere die Manipu-

lation des Bindungsverhaltens zu erwähnen, welches sowohl vom deklarierten Typ als auch vom Ob-

jekt, das referenziert wird, abhängt. Im Allgemeinen decken die durch das Mutation Testing vorge-

nommenen Änderungen typische Programmierfehler bei objektorientierten Programmen ab, die häufig

in Verbindung mit falsch eingesetzter Vererbung, Überladung von Methoden, deren Spezifikationen

voneinander abweichen, und Verdecken von Attributen stehen.

Listing 2 zeigt ein Beispiel für die Manipulation von statischer Methodenbindung. Der Aufruf der

Methode callMe wird zur Übersetzungszeit an callMe(Object) gebunden, da nur sie außerhalb des

Pakets sichtbar ist. Wird die Sichtbarkeit von callMe(String) auf public erhöht, findet ein Um-

binden von callMe(Object) auf callMe(String) statt, da der übergebene Wert vom Typ String

genau dem Typ des Parameters entspricht. Die Erhöhung der Sichtbarkeit kann beim Mutation Testing

ausgenutzt werden, um das Bindungsverhalten zu ändern. Ob sich dann tatsächlich das von außen

sichtbare Programmverhalten ändert, welches von Unit Tests überprüft werden kann, hängt von der

Implementierung ab.

Page 12: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

6 2 Grundlagen

package de;

public class StaticBinding {

public void callMe(Object obj) { }

void callMe(String string) { }

}

package en;

public class Foo {

public static void main(String[] args) {

new de.StaticBinding().callMe("Hello World");

}

}

Listing 2 Statische Bindung eines Methodenaufrufs

Im Gegensatz zu Unit Tests wird beim Mutation Testing nicht die Korrektheit der Implementierung

eines Programms getestet. Vielmehr wird geprüft, ob die Unit Tests die durch das Mutation Testing

hervorgebrachten, fehlerhaften Änderungen am Verhalten eines Programms entdecken. Dies ist wich-

tig, da sich während der Entwicklung und Wartung unerwünscht Änderungen am Programm einschlei-

chen können und von den Unit Tests entdeckt werden sollen. Eine grundlegende Basis an Unit Tests

wird daher vorausgesetzt, wenn aus dem Mutation Testing Nutzen gezogen werden soll.

Nun ist die Anzahl aller möglichen Änderungen eines Programms, welche zu einem fehlerhaften Ver-

halten führen können, potenziell unendlich groß, so dass sich auf eine Teilmenge von ihnen beschränkt

werden muss. In [J+H] wurden Analysen und Ergebnisse verschiedener, englischsprachiger Arbeiten,

die sich mit dem Thema Mutation Testing auseinandersetzen, zusammenfassend betrachtet. Darunter

findet sich die "Mutation Coupling Effect Hypothesis" aus [Off92], die besagt, dass der Zusammen-

hang zwischen komplexen Mutanten und einfachen Mutanten darin besteht, dass eine aus Unit Tests

bestehende Testbasis, die alle einfachen Mutanten erkennt, auch einen hohen Prozentsatz der komple-

xen Mutanten erkennen wird. Einfache Mutanten verursachen simple Fehler und zeichnen sich durch

wenige syntaktische und semantische Änderungen am Programm aus. Die Erhöhung der Sichtbarkeit

der Methode callMe(String) aus Listing 2 auf public resultiert in solch einen einfachen Mutan-

ten dar. Dieser Zusammenhang zwischen einfachen und komplexen Mutanten wird beim Mutation

Testing genutzt, um die Anzahl zu erzeugender Mutanten und somit auch die Laufzeitkosten zu redu-

zieren, indem lediglich wenige Änderungen am Programm vorgenommen werden.

Die grundsätzliche Vorgehensweise beim Mutation Testing zeigt Algorithmus 1. Die Testbasis beste-

hend aus Unit Tests erkennt das fehlerhafte Verhalten der Mutanten dadurch, dass mindestens ein Test

fehlschlägt, sobald die Testbasis auf den Mutanten ausgeführt wird. Dazu ist es nötig, dass das Pro-

gramm vor Ausführung des Mutation Testing alle Tests erfüllt. Ansonsten kann es zu falschen Ergeb-

nissen kommen. Welche Mutanten zu einem Programm gefunden werden und wie diese bestimmt

werden ist abhängig von dem verwendeten Verfahren. Diese Arbeit stützt sich auf das constraint-

basierte Verfahren zur Generierung von Mutanten (s. Abschnitt 2.4).

Page 13: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 7

algorithm mutationTesting(programm, testbasis)

precondition

testbasis darf keine Tests enthalten, die fehlschlagen

end precondition

mutant := suche Mutant zum programm

while mutant gefunden do

if mutant nicht kompilierbar then

ergebnis := mutant ungültig

else

führe testbasis auf mutant aus

if testbasis hat fehlgeschlagene Tests then

ergebnis := mutant getötet

else

ergebnis := mutant nicht getötet

end if

end if

sammle ergebnis

mutant := suche nächsten Mutant zum programm

end while

gib gesammelte Ergebnisse zurück

end mutationTesting.

Algorithmus 1 Mutation Testing mit Vorbedingungen, allgemein

Die Auswertung eines Mutanten führt zu einem der folgenden Ergebnisse.

Der Mutant wurde getötet

Der Mutant wurde von der Testbasis durch einen fehlgeschlagenen Test entdeckt. Die Testabde-

ckung ist ausreichend, um das fehlerhafte Verhalten des Mutanten festzustellen.

Der Mutant wurde nicht getötet

Die Testbasis hat den Mutanten nicht entdeckt, da kein Test fehlgeschlagen ist. Es kann sein, dass

das nach außen sichtbare Verhalten des Mutanten dem des ursprünglichen Programms gleicht. Der

Mutant wäre dann äquivalent. Dies kann nur manuell durch den Entwickler festgestellt werden, in-

dem er den Mutanten eingehend untersucht und diesen mit dem ursprünglichen Programm ver-

gleicht.

Der Mutant ist ungültig

Der Mutant ist nicht kompilierbar, weil er die Sprachspezifikation der Programmiersprache nicht

erfüllt. Mit dieser Art von Mutanten kann keine Aussage über die Testbasis getroffen werden.

Ein wesentliches Problem beim Mutation Testing ist der zusätzliche Aufwand, den der Entwickler

treiben muss, um aus den durch die Testbasis nicht entdeckten Mutanten die äquivalenten herauszufil-

tern. Dies ist mit ein Grund, warum sich das Mutation Testing bislang nicht in der Praxis durchsetzen

konnte. Viele Verfahren, so auch das in Abschnitt 2.4 beschriebene constraint-basierte Verfahren,

zielen darauf ab, die Anzahl der äquivalenten Mutanten und damit den manuellen Aufwand des Ent-

wicklers beim Aufspüren solcher zu reduzieren. Da jeder gültige Mutant gegen die Testbasis ausge-

Page 14: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

8 2 Grundlagen

führt wird, reduzieren sich mit der Anzahl generierter, äquivalenter Mutanten auch die Laufzeitkosten.

Diese können noch weiter reduziert werden, wenn ungültige Mutanten gar nicht erst erzeugt werden.

Selbst äquivalente Mutanten, obwohl für das Mutation Testing selbst nicht relevant, sind in manchen

Fällen für den Entwickler hilfreich wie das folgende Beispiel zeigt. Aus dem Programmcode in Listing

3 kann ein äquivalenter Mutant generiert werden, indem der Typ des Parameters value durch einen

abstrakteren Typ (z. B. Iterable<Object>) ersetzt wird. Die Wiederverwendbarkeit des Pro-

grammcodes wird erhöht, wenn die durch den Mutanten hervorgebrachte Änderung für das Programm

selbst übernommen wird, da dann Referenzen auf beliebige Objekte, die Iterable<Object> imple-

mentieren, als Werte an die Methode übergeben werden können.

public class PrintStreamExtension {

public static void println(PrintStream stream, List<Object> values) {

for (Object o : values) {

stream.println(o);

}

}

}

Listing 3 Programmcode als Basis für äquivalente Mutanten

2.3 Constraint-basierte Refaktorisierung

Die Entwicklung von Software erfolgt über einen längeren Zeitraum. Während der Zeit ändern sich

Anforderungen und neue kommen hinzu. Während die Code-Basis wächst, verschlechtert sich die

Code-Qualität, da Methoden und Klassen durch Hinzufügen neuer Funktionen immer größer werden,

was zu Lasten der Lesbarkeit und Wartbarkeit geht. Im Laufe der Zeit kann sich auch die Sicht auf die

Anwendungsdomäne verändern, sodass die Struktur des Codes die Problemwelt stellenweise nicht

mehr wiederspiegelt. Erweiterungen der Software werden dann umso aufwändiger. Um diesem Prob-

lem entgegenzuwirken, muss der Code entwicklungsbegleitend gepflegt, überarbeitet und umstruktu-

riert werden.

Refaktorisierungen stellen Änderungen am Code dar, die unter Beibehaltung des Programmverhaltens

zum Zwecke einer höheren Code-Qualität durchgeführt werden. Der Code wird dahingehend ange-

passt, dass er leichter zu lesen, zu warten und / oder zu erweitern ist.

Die Durchführung von Refaktorisierungen ohne Unterstützung von Werkzeugen kann schnell dazu

führen, dass das Programmverhalten unbeabsichtigt verändert wird. Im besten Fall fällt dies dadurch

auf, dass das Programm nicht mehr kompilierbar ist. Ansonsten können, eine ausreichende Testabde-

ckung vorausgesetzt, fehlschlagende Unit Tests die Abweichung des Programmverhaltens aufdecken.

Das selbst einfache Refaktorisierungen Fehler verursachen können zeigt Listing 4. Wird die Methode

renameToPrint(String) umbenannt in print(String), bindet der Compiler den Methodenauf-

ruf in main(String[]) nicht mehr an die Methode print(Object) sondern an die Methode

print(String), da die übergebende Referenz auf ein Objekt vom Typ String zeigt, welcher mit

dem des Methodenparameters übereinstimmt.

Page 15: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 9

public class RenameMethod {

private void print(Object obj) {

System.out.println(obj);

}

private void renameToPrint(String str) {

System.out.println("Boo");

}

public static void main(String[] args) {

new RenameMethod().print("Hallo Welt");

}

}

Listing 4 Umbenennung einer Methode

Moderne Entwicklungsumgebungen wie Eclipse unterstützen mit Hilfe von Werkzeugen den Entwick-

ler bei Refaktorisierungen. Dabei sollte sich der Entwickler darauf verlassen können, dass die von

ihnen durchgeführten Refaktorisierungen das Programmverhalten nicht beeinflussen. Fehlerhafte Re-

faktorisierungen sollten verhindert werden. Leider können dies heutige Werkzeuge wie in [Ste10]

gezeigt nicht in jeder Situation garantieren. Die Refaktorisierung führt dann entweder zu einem nicht

kompilierbaren Programm oder verändert sein Verhalten, was im schlimmsten Fall nicht (sofort) ent-

deckt wird.

Eine Lösung für dieses Problem verspricht die constraint-basierte Refaktorisierung. Ein Constraint

stellt dabei eine logische Bedingung dar, die zu wahr oder falsch ausgewertet wird. Es enthält eine

oder mehrere Constraint-Variablen, die mit beliebigen Werten aus ihren Wertebereichen belegt wer-

den können. Gibt es für ein Constraint mindestens eine Belegung der Constraint-Variablen mit Wer-

ten, sodass es erfüllt ist, wird das Constraint als lösbar bezeichnet. Im Allgemeinen werden viele un-

tereinander in Beziehung stehende Constraints gleichzeitig betrachtet und als ein Constraint-System

aufgefasst. Dieses beschreibt dann ein logisches Problem, zu dem Lösungen gesucht werden. Ein

Constraint-System ist genau dann lösbar, wenn die in ihm vorkommenden Constraint-Variablen so mit

Werten belegt werden können, dass alle im Constraint-System enthaltenen Constraints lösbar sind.

In Listing 5 ist ein einfaches Constraint-System dargestellt, das aus vier Constraints und drei

Constraint-Variablen besteht. Die Menge accessibility sowie die Ordnung ihrer Elemente zuei-

nander orientieren sich an den Sichtbarkeitsmodifikatoren in Java. Den Constraint-Variablen

accessibility(x), accessibility(y) und accessibility(z) können beliebige Werte aus

dem zugehörigen Wertebereich zugeordnet werden. Diesen Constraint-Variablen werden nun systema-

tisch verschiedene Werte zugewiesen. Währenddessen wird geprüft, ob das Constraint-System mit der

jeweiligen Wertebelegung erfüllt ist, also alle Constraints zu wahr ausgewertet werden. Trifft dies zu,

wurde eine Lösung gefunden, die aus der jeweiligen Wertebelegung der Constraint-Variablen besteht.

Page 16: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

10 2 Grundlagen

public = accessibility(x)

accessibility(x) > accessibility(y)

accessibility(y) ≥ accessibility(z)

accessibility(z) > private

accessibility = { public, protected, package, private |

public > protected > package > private }

Listing 5 Constraint-System mit Wertebereich der Constraint-Variablen

Bei der constraint-basierten Refaktorisierung werden die Beziehungen zwischen den Programmele-

menten, wie z. B. Methoden und Attribute, eines Programms auf Constraints, die von Constraint-

Regeln erzeugt werden, abgebildet. Grundsätzlich lassen sich zwei Arten von Constraints unterschie-

den.

Syntaktische und semantische Constraints

Syntaktische und semantische Constraints stellen die Korrektheit eines Programms bezüglich der

zugrundeliegenden Programmiersprache sicher, d. h. ihre Erfüllung gewährleistet, dass das Pro-

gramm kompilierbar ist.

Bindungserhaltene Constraints

Die bindungserhaltenen Constraints sorgen dafür, dass das Programmverhalten beibehalten wird.

Ein Umbinden an andere Programmelemente, wie z. B. an eine andere Methode, sei es statisch zur

Übersetzungszeit oder dynamisch zur Laufzeit, wird unterbunden.

Die Constraint-Regeln spiegeln die Regeln der Sprachspezifikation einer Programmiersprache wieder.

Aus einem Programm kann unter Zuhilfenahme der Constraint-Regeln ein Constraint-System erzeugt

werden. Jede Constraint-Variable stellt dabei eine Eigenschaft eines Programmelements dar. Eine

gewünschte Änderung am Programm wird durch die Manipulation der Werte der Eigenschaften vor-

genommen. Unter der Voraussetzung, dass die Constraint-Regeln vollständig und korrekt sind, ist ein

Programm genau dann kompilierbar und verhaltensäquivalent zum ursprünglichen Programm, wenn

die Wertebelegung der Constraint-Variablen eine Lösung für das aus ihm erzeugte Constraint-System

darstellt. Dies wird durch die Constraints sichergestellt.

Eine Refaktorisierung wird durchgeführt, indem Werte von Eigenschaften bestimmter Programmele-

mente verändert werden. Die Veränderung der Sichtbarkeit einer Klasse x erfordert, dass die

Constraint-Variable accessibility(x) auf einen neuen Wert gesetzt wird. Das kann unter Um-

ständen die Veränderung von Werten weiterer Eigenschaften anderer Programmelemente nach sich

ziehen, falls die aktuelle Wertebelegung des Constraint-Systems keine Lösung darstellt. Dies zieht

sich solange fort, bis entweder eine Lösung gefunden wurde, oder feststeht, dass keine Lösung erreicht

werden kann. Im letzteren Fall wird die Refaktorisierung verweigert.

Page 17: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 11

2.4 Von der constraint-basierten Refaktorisierung zum Mutant

Das im Abschnitt 2.3 betrachtete Verfahren der constraint-basierten Refaktorisierung kann als Basis

für das Mutation Testing eingesetzt werden, um die Generierung von ungültigen und äquivalenten

Mutanten möglichst zu vermeiden, was die Laufzeitkosten des Mutation Testing und den manuellen

Aufwand zum Aussortieren nicht verwertbarer Mutanten erheblich reduziert.

Ein Mutant im Sinne des Mutation Testing ist ein Programm, das durch absichtliche in der Regel au-

tomatisiert durchgeführte Änderungen an der Code-Basis eines anderen Programms aus diesem her-

vorgegangen ist, mit dem Ziel zu prüfen, ob diese Änderungen am Programm von einer Testbasis er-

kannt werden. Das Programm, aus dem Mutanten generiert werden, wird als ursprüngliches Programm

bezeichnet.

Von Interesse für das Mutation Testing sind solche Mutanten, die eine nach außen hin beobachtbare

Verhaltensänderung aufweisen. Nur diese Art von Mutanten kann auch von einer Testbasis entdeckt

werden, da automatisierte Tests auf der Überprüfung von Annahmen basieren, die sich auf den Zu-

stand oder das Verhalten einer oder mehrerer Klassen beziehen. Es werden mehrere Arten von Mutan-

ten unterschieden. Die Aufteilung orientiert sich dabei an [Bär10].

Gültige Mutanten

Mutanten, die den syntaktischen und semantischen Regeln der zugrundeliegenden Programmier-

sprache entsprechen, sind korrekte Programme im Sinne der Sprachspezifikation und werden als

gültig bezeichnet. Jeder gültige Mutant ist damit kompilierbar.

Page 18: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

12 2 Grundlagen

Äquivalente Mutanten

Äquivalente Mutanten unterscheiden sich zwar syntaktisch und / oder semantisch vom ursprüngli-

chen Programm, weisen aber ein von außen beobachtbares Verhalten auf, das dem des ursprüngli-

chen Programms gleicht. Es wird davon ausgegangen, dass ein Vergleich des Programmverhaltens

nur zwischen kompilierbaren und zumindest von einer Testbasis ausführbaren Programmen mög-

lich ist. In diesem Sinne ist jeder äquivalente Mutant auch gültig. In Listing 6 wird ein Programm

einem daraus generierten äquivalenten Mutanten gegenübergestellt. Die Sichtbarkeit der Methode

log(String) wurde auf private reduziert. Dies hat keinerlei Auswirkungen auf die Bindung

des Methodenaufrufs. Das Verhalten des Mutanten gleicht dem des ursprünglichen Programms.

class Logger {

public void log(Object obj) {

System.out.println(obj);

}

public void log(String str) {

System.out.println(str);

}

}

class Foo {

void doSomething() {

Logger myLogger =

new Logger();

myLogger.log(new Object());

}

}

class Logger {

public void log(Object obj) {

System.out.println(obj);

}

private void log(String str) {

System.out.println(str);

}

}

class Foo {

void doSomething() {

Logger myLogger =

new Logger();

myLogger.log(new Object());

}

}

Listing 6 Ursprüngliches Programm (links) und äquivalenter Mutant (rechts)

Page 19: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 13

Relevante Mutanten

Relevante Mutanten weisen ein dem ursprünglichen Programm abweichendes Bindungsverhalten

auf. Dies schließt sowohl die statische Bindung, die zur Übersetzungszeit erfolgt, als auch die dy-

namische Bindung während der Laufzeit ein. Das Umbinden eines Methodenaufrufs führt z. B. zu

einem relevanten Mutanten. Eine Änderung der Bindung resultiert aber nicht zwangsläufig auch in

einem anderen Verhalten. Ein relevanter Mutant kann ebenfalls verhaltensäquivalent zum ur-

sprünglichen Programm und damit auch ein äquivalenter Mutant sein. In jedem Fall ist er kompi-

lierbar und somit gültig. Listing 7 zeigt ein Programm mit einem daraus resultierenden relevanten

Mutanten. In der Methode doSomething() wurde der ursprüngliche Typ Stack durch seinen

Subtyp ReadonlyStack ersetzt. Dadurch wird der Aufruf von add(Object) zur Laufzeit an die

im Subtyp überschriebene Methode gebunden, was zu einem anderen Programmverhalten führt.

class Stack {

boolean add(Object value) {

return false;

}

}

class ReadonlyStack extends Stack {

boolean add(Object value) {

throw new

RuntimeException();

}

}

class Foo {

void doSomething() {

Stack myStack = new Stack();

myStack.add(new Object());

}

}

class Stack {

boolean add(Object value) {

return false;

}

}

class ReadonlyStack extends Stack {

boolean add(Object value) {

throw new

RuntimeException();

}

}

class Foo {

void doSomething() {

Stack myStack = new

ReadonlyStack();

myStack.add(new Object());

}

}

Listing 7 Ursprüngliches Programm (links) und relevanter Mutant (rechts)

Für das Mutation Testing ist nur die Teilmenge der relevanten Mutanten von Bedeutung, deren Ver-

halten sich von dem des ursprünglichen Programms unterscheidet. Ob sich ein relevanter Mutant tat-

sächlich anders verhält, muss vom Entwickler untersucht werden, denn ein Vergleich der von ver-

schiedenen Programmen berechneten Funktionen ist nicht möglich [Wei11]. Die manuelle Untersu-

chung vieler potenziell äquivalenter Mutanten durch den Entwickler ist sehr zeitaufwendig.

Die Anpassung der bei der constraint-basierten Refaktorisierung verwendeten Vorgehensweise für das

Mutation Testing kann die Anzahl der generierten äquivalenten und ungültigen Mutanten erheblich

reduzieren [S+T10]. Tatsächlich gibt es zwischen dem Problem nur gültige verhaltensbeibehaltene

Refaktorisierungen zu erlauben und dem Problem möglichst nur relevante Mutation zu generieren

Page 20: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

14 2 Grundlagen

einige Gemeinsamkeiten. Beiden Problemen ist gemein, dass als Resultat ein kompilierbares Pro-

gramm entstehen soll. Bei der constraint-basierten Refaktorisierung wird dies durch Erfüllung der im

Constraint-System vorhandenen syntaktischen und semantischen Constraints, die aus den an der

Sprachspezifikation der Programmiersprache angelehnten Constraint-Regeln erzeugt werden, sicher-

gestellt. Die Übernahme der Constraint-Regeln für das Mutation Testing gewährleistet dann, dass nur

noch gültige Mutation generiert werden. Die bindingserhaltenen Constraints sorgen dafür, dass das

Programmverhalten durch Beibehalten statischer und dynamischer Bindung unverändert bleibt. Dies

wird beim Mutation Testing ausgenutzt, indem ein bindungserhaltenes Constraint negiert wird, was in

einem veränderten Bindungsverhalten des Programms resultiert. Das negierte Constraint ist genau

dann erfüllt, wenn das ursprüngliche Constraint nicht erfüllt ist. Logisch gesehen wird ein Constraint

durch Anwendung des Not-Operators negiert.

Zur Generierung eines Mutanten wird ein bindungserhaltenes Constraint aus dem Constraint-System

ausgewählt und negiert. Ein aus einem kompilierbaren Programm erzeugtes Constraint-System ist mit

der initialen Wertebelegung der Constraint-Variablen lösbar. Die Negierung eines Constraints führt

dazu, dass mindestens eine Constraint-Variable neu belegt werden muss, um eine Lösung zu erhalten.

Dies führt zu einer kleinen syntaktischen und / oder semantischen Veränderung des Programms, einem

einfachen Mutanten. Die Beibehaltung sämtlicher anderer Constraints gewährleistet, dass eine Lösung

des Constraint-Systems zu einem gültigen Mutanten führt. Wie auch bei der constraint-basierten Re-

faktorisierung kann die Suche nach einer Lösung des Constraint-Systems Änderungen an den Werten

weiterer Constraint-Variablen nach sich ziehen. Äquivalente Mutanten können mit diesem Verfahren

zwar nicht vermieden werden, ein Umbinden von Aufrufen gewährleistet keine Änderung des nach

außen sichtbaren Programmverhaltens, aber zumindest wird ihre Anzahl reduziert, da Mutanten mit

unverändertem Bindungsverhalten nicht generiert werden. Es müssen nun nur noch alle bindungser-

haltenen Constraints systematisch ausgewählt und negiert werden, um möglichst viele relevante Mu-

tanten zu erhalten.

2.5 Refacola

Die Refactoring Constraint Language (Refacola) wird an der Fernuniversität Hagen im Lehrgebiet

Programmiersysteme entwickelt und ist eine domänenspezifische Sprache (Domain Specific Langua-

ge, DSL), die es ermöglicht constraint-basierte Refaktorisierungen deklarativ zu spezifizieren. Dabei

beschränkt sich Refacola nicht nur auf eine einzige Programmiersprache. Werkzeuge für Refaktorisie-

rungen ermöglichen im Allgemeinen nur die Umstrukturierung von Code innerhalb einer Program-

miersprache. Eines der Ziele von Refacola ist die Unterstützung von Refaktorisierungen über die

Grenzen einer Programmiersprache und sogar über verschiedene Programmierparadigmen (objektori-

entiert, funktional, ...) hinaus. Durch die Verwendung des constraint-basierten Ansatzes werden feh-

lerhafte Refaktorisierungen, wie sie in bestimmten Situationen von den in Eclipse integrierten Werk-

zeugen durchgeführt werden, vermieden.

Refacola stellt eine gute Basis für Mutation Testing dar, da viele Komponenten, die benötigt werden,

bereits vorhanden sind und von den von ihr durchgeführten Refaktorisierungen verwendet werden.

Der Aufbau des Constraint-Systems aus einem Programm und die Verwendung eines Constraint-

Solvers, der mögliche Lösungen eines Constraint-Systems berechnet, zählen dazu. Wie in Abschnitt

2.4 erläutert können dieselben Constraint-Regeln, die für constraint-basierte Refaktorisierungen for-

muliert wurden, auch für das Mutation Testing herangezogen werden. Ebenso ist eine Komponente

vorhanden, welche nach Auswahl einer Lösung die Änderungen am Programm zurückschreibt.

Page 21: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 15

In Refacola gibt es zwei Bereiche, die für jede Programmiersprache definiert werden müssen. Dabei

handelt es sich einerseits um die Sprachdefinition, welche die verschiedenen Programmelemente einer

Programmiersprache beschreibt. Programmelemente sind Ausprägungen einzelner Typen. In objekt-

orientierten Programmiersprachen stellen Klassen, Methoden oder auch Attribute Entitäten dar, die

sich Typen zuordnen lassen. Jeder Typ verfügt über eine gewisse Anzahl von Eigenschaften, denen

wiederum ein Wertebereich zugeordnet ist. Eine Klasse besitzt beispielsweise eine Eigenschaft, die

ihren Namen repräsentiert und dessen Wertebereich die gültigen Bezeichner der jeweiligen Program-

miersprache umfasst. Analog zur objektorientierten Denkweise stellt ein Typ einer Sprachdefinition

genau eine Klasse innerhalb eines Programms dar, wobei die Typen im Gegensatz zu Klassen keinerlei

Verhalten besitzen. Darüber hinaus können Abfragen als Prädikate definiert werden, aus denen Fakten,

welche u. a. die Beziehungen zwischen Programmelementen darstellen können, abgeleitet werden.

Beispielsweise existiert in der Sprachdefinition für Java eine Abfrage, die auswertet, ob ein Pro-

grammelement an ein anderes Programmelement gebunden ist.

Listing 8 zeigt einen kleinen Ausschnitt der Refacola Sprachdefinition für Java. Wie ansatzweise zu

sehen ist, werden die Programmelemente von Java in Refacola hierarchisch definiert. Dadurch können

einerseits die Typen als Filter in Constraint-Regeln verwendet werden, was der Polymorphie in ob-

jektorientierten Programmiersprachen gleicht, andererseits soll möglichst die Terminologie aus der

Java Sprachspezifikation übernommen werden. Ein Programmelement, das eine Klasse repräsentiert,

ist damit (auch) vom Typ AccessibleEntity, da es ebenfalls über die Eigenschaft

accessibility verfügt, welches die Sichtbarkeit der Klasse darstellt.

language Java

kinds

abstract Entity <: ENTITY

abstract AccessibleEntity <: Entity { accessibility }

abstract StaticMember <: Member

abstract TypedStaticMember <: StaticMember, TypedMember

properties

accessibility "\\alpha" : AccessModifier

domains

AccessModifier = {private, package, protected, public}

queries

ltPublic(A : AccessibleEntity)

Listing 8 Ausschnitt aus der Refacola Sprachdefinition für Java

Eine andere Sicht auf den Ausschnitt der Sprachdefinition in Listing 8 stellt Abbildung 1 dar. Hier ist

gut die Analogie zwischen den Typen in der Sprachdefinition und den Klassen in objektorientierten

Programmen zu sehen. Zu beachten ist, dass entgegen den Möglichkeiten, die Java bietet, Mehrfach-

vererbung bei der Definition von Typen möglich ist. Dieser Umstand wird bei der Generierung von

Java-Quelltext aus der Refacola Sprachdefinition insofern berücksichtigt, als dass jeder Typ auf ein

Interface abgebildet wird, das ggf. mehrere Interfaces erweitert (Schnittstellenvererbung).

Page 22: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

16 2 Grundlagen

Abbildung 1 Alternative Darstellung des Ausschnitts aus der Refacola Sprachdefinition für Java

Der andere in Refacola zu definierende Bereich für eine Programmiersprache enthält die Constraint-

Regeln. Sie basieren auf der ebenfalls in Refacola definierten Sprachdefinition der jeweiligen Pro-

grammiersprache und werden verwendet, um das Constraint-System eines Programms zu erzeugen.

Jede Constraint-Regel besitzt einen eindeutigen Namen, der vom Refacola Entwickler frei vergeben

werden kann, und teilt sich in zwei Abschnitte, dem Deklarationsteil und dem Regelrumpf. Im ersten

Abschnitt werden Variablen deklariert, die innerhalb der Constraint-Regel verwendet werden. Diese

sind typisiert, wobei ihr Typ aus der Refacola Sprachdefinition stammen muss, wie z. B.

AccessibleEntity aus Listing 8. Der Regelrumpf unterteil sich wiederum in einen Bedingungsteil

und einen Aktionsteil. Im Bedingungsteil können beliebige Abfragen aus der Sprachdefinition durch

Kommata getrennt verwendet werden. Sie werden automatisch mittels logischem und-Operator zu

einer Bedingung verknüpft. Vom Typ passende Programmelemente werden wie bei logischen Pro-

grammiersprachen an die Variablen gebunden. Erfüllen die Programmelemente die Bedingung, wer-

den die im Aktionsteil definierten Constraints für die in den Variablen gebundenen Programmelemen-

te erzeugt.

In Listing 9 ist eine Constraint-Regel angegeben, welche die Forderung, dass in Interfaces deklarierte

Programmelemente die Sichtbarkeit public besitzen müssen, deklarativ formuliert. Falls I an ein

Programmelement vom Typ Interface und M an ein Programmelement vom Typ Member gebunden

wird und M in I deklariert ist, dies entspricht der Abfrage Java.member(), dann wird für das an M

gebundene Programmelement ein Constraint erzeugt, welches genau dann erfüllt ist, wenn M die Sicht-

barkeit public besitzt.

AccessibleEntity

+accessibility: AccessModifier

AccessModifier<<enumeration>>

+private+package+protected+public

Entity Member

StaticMember

TypedStaticMember

TypedMember

Page 23: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

2 Grundlagen 17

OOPSLA_f0_interfaceMemberAccessibility

for all

I: Java.Interface

M: Java.Member

do

if

Java.member(I, M)

then

M.accessibility = # public

end

Listing 9 Constraint-Regel für die Sichtbarkeit von in Interfaces deklarierten Programmelementen

Listing 10 zeigt ein Code-Beispiel mit Programmelementen, auf die die Constraint-Regel zutrifft. Das

Interface MyInterface stellt ein Programmelement dar, das an die Variable I gebunden wird. Eben-

so wird die Methode myMethod() an M gebunden. Da myMethod() in MyInterface deklariert und

somit ein Mitglied von MyInterface ist, ist die Bedingung der Constraint-Regel erfüllt und das im

Aktionsteil definierte Constraint wird erzeugt. myMethod() ist an M gebunden, was letztendlich in

einem Constraint resultiert, das genau dann erfüllt ist, wenn myMethod() die Sichtbarkeit public

besitzt. Die in Listing 9 gezeigte Constraint-Regel erzeugt semantische Constraints, deren Nichterfül-

lung ein nicht kompilierbares Programme zur Folge hätte.

public interface MyInterface /* I */ {

void myMethod(); // M

}

Listing 10 Beispiel zur Constraint-Regel

Neben den beiden betrachteten Bereichen, der Sprachdefinition und den Constraint-Regeln, gibt es

noch einen weiteren, in dem Refaktorisierungen definiert werden. Eine Refaktorisierung kann in Refa-

cola als Änderung der Werte von Eigenschaften ausgewählter Programmelemente betrachtet werden.

Die Refaktorisierung "Methode umbenennen" würde der identifier Eigenschaft des Programm-

elements, das die ausgewählte Methode repräsentiert, den vom Entwickler gewünschten Namen zu-

weisen. Ferner kann definiert werden, inwieweit Eigenschaften weiterer Programmelemente im Zuge

der Suche nach einer Lösung des Constraint-Systems neue Werte zugewiesen werden dürfen. Spezifi-

zierungen von Refaktorisierungen in Refacola werden in dieser Arbeit nicht weiter betrachtet, da sie

für das Mutation Testing nicht verwendet werden.

Page 24: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

18 2 Grundlagen

Page 25: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 19

3 Implementierung

Refacola soll im Rahmen dieser Arbeit um eine Komponente zur Durchführung von Mutation Testing

erweitert werden. Wie auch Refacola selbst, wird das Mutation Testing Framework, wie die Kompo-

nente nachfolgend genannt wird, als Plugin für die Entwicklungsumgebung Eclipse realisiert. Im Vor-

feld sind die Anforderungen zu spezifizieren, welche von dem Framework erfüllt werden sollen. Diese

werden in Abschnitt 3.1 genauer betrachtet.

Während der Entwicklung hat sich herausgestellt, dass eine Aufteilung des Mutation Testing Frame-

works in mehrere Eclipse-Plugins eine bessere Wiederverwendung und eine saubere Trennung zwi-

schen den verschiedenen Komponenten ermöglicht. Gleichzeitig orientiert sich die Aufteilung an die

Strukturierung der Eclipse-Plugins, in die Refacola unterteilt ist. Eine Übersicht über die verschiede-

nen Eclipse-Plugins des Frameworks findet sich am Anfang von Abschnitt 3.2.

Auf den Anwendungskern wird in Abschnitt 3.2.1 näher eingegangen. Es werden die einzelnen Schrit-

te zur Vollziehung eines Mutation Testing Durchlaufs im Detail erklärt. Die Brücke zu Refacola wird

hergestellt, indem aufgezeigt wird, welche bereits dort vorhandenen Klassen und Mechanismen ver-

wendet werden.

Die Ausführung einer Testbasis stellt einen wichtigen Schritt beim Mutation Testing dar. Andererseits

ist diese Funktionalität allgemein genug, dass sie ebenfalls in einem anderen Kontext verwendet wer-

den kann. Sie wurde daher in einem eigenen Eclipse-Plugin, dem JUnit Test Runner, ausgelagert. Die

Interna des JUnit Test Runner sind in Abschnitt 3.2.2 beschrieben.

Ein weiterer wichtiger Teil des Mutation Testing Frameworks stellt die Benutzungsoberfläche dar, die

sich als Eclipse-View möglichst harmonisch in Eclipse integrieren soll. Als eine von vielen Views in

Eclipse soll sie den Entwickler bei der Arbeit unterstützen. Es wurde auf modale Dialoge zugunsten

einer angenehmen Benutzbarkeit verzichtet. Abschnitt 3.2.3 behandelt den Aufbau und die Implemen-

tierung der Benutzungsoberfläche.

Das Mutation Testing Framework soll dem Refacola-Entwickler die Möglichkeit geben, die in Refaco-

la bereits definierten und zukünftig noch hinzukommenden Constraint-Regeln für das Mutation Tes-

ting auszuzeichnen. Dabei wurde Wert darauf gelegt, dass Auszeichnungen so einfach wie möglich

durchzuführen sind ohne auf Flexibilität verzichten zu müssen. Die Auszeichnung der Constraint-

Regeln und was dabei zu beachten ist, wird in Abschnitt 3.3 näher untersucht.

Nachdem ein Überblick über die Funktionalität des Mutation Testing Frameworks gegeben wurde,

wird anhand eines kleinen Beispiels in Abschnitt 3.4 die Anwendung demonstriert. Das ursprüngliche

Java-Programm, das verwendet wird, wird einem Mutanten gegenübergestellt.

Die Implementierung des Mutation Testing Frameworks unterliegt einigen Einschränkungen. Obwohl

Refacola nicht auf eine konkrete Programmiersprache festgelegt ist, kann das Framework in ihrer jet-

zigen Form nur auf in Java geschriebene Programme angewendet werden. Weiterhin zieht sie keinen

Vorteil aus Mehrkernprozessoren, da das Mutation Testing sequenziell durchgeführt wird. Gründe

dafür und Ansätze, um den Ablauf zu parallelisieren, werden in Abschnitt 3.5 betrachtet.

Page 26: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

20 3 Implementierung

3.1 Anforderungen

Wie bereits am Anfang von Kapitel 3 erläutert, soll für Refacola ein Framework entwickelt werden,

mit dessen Hilfe Mutation Testing durchgeführt werden kann. Dazu soll sich das Framework soweit

wie möglich auf bereits in Refacola vorhandene Funktionen stützen. Refacola selbst ist nicht an eine

konkrete Programmiersprache gebunden. Ziel des Mutation Testing Frameworks im Rahmen dieser

Arbeit ist die Unterstützung von Java-Programmen.

Das Mutation Testing Framework soll dem Refacola-Entwickler die Möglichkeit geben, die in Refaco-

la bereits definierten und zukünftig noch zu definierenden Constraint-Regeln deklarativ auszuzeich-

nen. Dadurch werden indirekt die von den ausgezeichneten Constraint-Regeln erzeugten Constraints

bestimmt, die im Laufe eines Mutation Testing Durchlaufs negiert werden. Constraints werden unab-

hängig voneinander negiert, d. h. zu jedem Zeitpunkt während eines Durchlaufs ist innerhalb des aus

einem Programm erzeugten Constraint-Systems nur ein Constraint in seiner negierten Form vorhan-

den. Dies bedeutet, dass aus dem ursprünglichen Constraint-System ein Constraint ausgewählt und

durch seine negierte Form ersetzt wird. Bevor das nächste Constraint negiert wird, werden die Ände-

rungen am Constraint-System wieder rückgängig gemacht. Dieses Verfahren wird in Abschnitt 3.2.1

noch ausführlicher betrachtet.

Anforderungen, die an das Mutation Testing Framework zu stellen sind, werden nachfolgend betrach-

tet.

Generischer Ansatz der Constraint-Negierung

Da beliebige in Refacola definierte Constraint-Regeln als negierbar ausgezeichnet werden sollen,

insbesondere auch solche, die zukünftig noch definiert werden, wird ein generischer Ansatz zur

Negierung der Constraints benötigt. Basis des Ansatzes sind Untersuchungen, die im Zuge der Mu-

tantengenerierung mit Type Constraints durchgeführt wurden (s. [Bär10]). Dabei können keine

Annahmen bezüglich der Constraint-Regeln gemacht werden. Es muss davon ausgegangen werden,

dass beliebige, sogar alle vorhandenen Constraint-Regeln ausgezeichnet werden. Auch sind die zu

manipulierenden Eigenschaften von Programmelementen nicht weiter bekannt. Diese können von

dem Namen eines Programmelements bis zu seiner Sichtbarkeit reichen.

Einfache Auszeichnung zu negierender Constraint-Regeln

Refacola befindet sich zum Zeitpunkt der Entstehung des Mutation Testing Frameworks in der

Entwicklung. Die vorhandenen Constraint-Regeln decken noch nicht die gesamte Sprachspezifika-

tion von Java ab. Es ist daher davon auszugehen, dass noch Constraint-Regeln hinzugefügt oder

geändert werden. Auch ist nicht sicher, ob die vorhandenen Constraint-Regeln korrekt sind. Die

Implementierung des Mutation Testing Frameworks sollte diese Umstände berücksichtigen, indem

die Auszeichnung von Constraint-Regeln möglichst einfach und ohne Eingriff in den Quellcode

möglich ist. Refacola bietet mit ihrer domänenspezifischen Sprache (domain specific language,

DSL) in Verbindung mit einem Compiler bereits die Möglichkeit Java-Quellcode zu generieren.

Auf diese Weise können Sprachdefinitionen, Constraint-Regeln und Refaktorisierungen deklarativ

in der DSL formuliert werden. Die dort definierten Elemente können ähnlich wie Programmele-

mente in Java (Klassen, Methoden, ...) mit Annotationen ausgezeichnet werden. Dieser Ansatz

scheint für die Auszeichnung von Constraint-Regeln sehr vielversprechend zu sein.

Page 27: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 21

Automatisierte Durchführung des Mutation Testing

Die Akzeptanz von Testmethoden hängt nicht zuletzt von ihrer einfachen und schnellen Durchfüh-

rung ab. Unit-Tests werden umso häufiger ausgeführt, je einfacher sie gestartet werden können. Ei-

ne manuell zu konfigurierende Testumgebung hat immer das Potenzial zusätzliche Fehler zu erzeu-

gen und erhöht den Aufwand, den ein Entwickler treiben muss, um einen Testdurchlauf anzusto-

ßen. Aus diesen Gründen soll das Mutation Testing möglichst automatisiert durchgeführt werden.

Eine Minimalkonfiguration ist allerdings nötig, da das zu testende Programm sowie die zu verwen-

dende Testbasis als Informationen benötigt werden. Ein Mutation Testing Durchlauf kann danach

vollautomatisiert unter der Voraussetzung erfolgen, dass die in der Testbasis enthaltenen Tests

nicht manuell konfiguriert werden müssen. Es wird davon ausgegangen, dass die Testbasis ledig-

lich aus Unit-Tests besteht, die per Definition keiner manuellen Konfiguration bedürfen.

Übersichtliche Darstellung der Ergebnisse des Mutation Testing

Die Ergebnisse des Mutation Testing sollen dem Entwickler Aufschluss über die generierten Mu-

tanten und deren Erkennung durch die Testbasis geben. Da das Mutation Testing Framework als

Plugin in Eclipse umgesetzt wird, bietet sich eine Eclipse-View zur Darstellung der Ergebnisse an.

Eine gute Integration erfordert auch, dass sich eine Eclipse-View nicht in den Vordergrund drängt.

Auf modale Dialog wird daher bei der Implementierung der Eclipse-View verzichtet. Es sollte so-

fort ersichtlich sein, welcher Mutant zu welchem Ergebnis geführt hat. Da das Interesse an den

nicht getöteten Mutanten am größten ist, schließlich könnten sie Hinweise zur Verbesserung der

Testabdeckung geben, sollte der Fokus auf diese Mutanten gelegt werden. Nicht getötete Mutanten

sind besonders zu kennzeichnen.

Vergleich von Änderungen an der Codebasis zwischen Mutant und ursprünglichem Programm

Ob ein nicht getöteter Mutant verhaltensgleich zum ursprünglichen Programm ist, muss der Ent-

wickler selbst untersuchen. Er kann dabei unterstützt werden, indem Änderungen im Quellcode auf

Basis des ursprünglichen Programms hervorgehoben werden und die betroffenen Stellen in einem

Eclipse-Editor angezeigt werden. Sollte sich herausstellen, dass der Mutant ein anderes Verhalten

aufzeigt, wird der Entwickler den Wunsch haben, zur Erhöhung der Testabdeckung weitere Unit-

Tests auszuarbeiten. Dabei sollte er die Möglichkeit haben, beliebig zwischen Mutanten und ur-

sprünglichem Programm hin- und herzuwechseln, um die syntaktischen und / oder semantischen

Unterschiede zu untersuchen.

3.2 Umsetzung

Die Implementierung des Mutation Testing Frameworks für Refacola erfolgt in Form von Plugins für

die Entwicklungsumgebung Eclipse. Dabei wird das Framework selbst in drei Plugins aufgeteilt, dem

Anwendungskern, dem JUnit Test Runner und der Benutzungsoberfläche, was einer sauberen Tren-

nung und besseren Wiederverwendung zugutekommt. Die Aufteilung von Anwendungskern und Be-

nutzungsoberfläche in zwei Plugins sowie deren Namensgebung orientieren sich an den Eclipse-

Plugins, in die Refacola aufgeteilt ist. Abbildung 2 stellt die Unterteilung des Mutation Testing Fra-

meworks in die drei genannten Plugins dar.

Page 28: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

22 3 Implementierung

Abbildung 2 Aufteilung des Mutation Testing Frameworks auf Eclipse-Plugins

Bevor in den nächsten Abschnitten, die sich an der Aufteilung der Eclipse-Plugins orientieren, detail-

lierter auf die einzelnen Bestandteile des Mutation Testing Frameworks eingegangen wird, folgt eine

kurze Übersicht über die Aufgabe jedes Plugins.

Anwendungskern (de.feu.ps.refacola.mutation)

Der Anwendungskern enthält die gesamte innere Logik des Mutation Testing Frameworks und ge-

neriert für beliebige in Java geschriebene Programme Mutanten auf Basis der in Refacola ausge-

zeichneten Constraint-Regeln. Er arbeitet sowohl mit Refacola als auch mit dem JUnit Test Runner

zusammen, wobei letzterer zur Ausführung der Testbasis verwendet wird. Die Aufgabe des An-

wendungskerns ist die Generierung von Mutanten und die Auswertung der Testergebnisse, die er

vom JUnit Test Runner für jeden Mutanten erhält. Als Ergebnis übergibt er die Resultate eines Mu-

tation Testing Durchlaufs zusammen mit den Mutationen, mit dessen Hilfe ein Programm in einen

durch die Mutation definierten Mutanten überführt werden kann, an den aufrufenden Code zurück.

Der Anwendungskern wird in Abschnitt 3.2.1 detailliert betrachtet.

JUnit Test Runner (de.feu.ps.refacola.mutation.junitrunner)

Der JUnit Test Runner ist für die Ausführung von JUnit-Tests eines ihm übergebenen Java-

Programms verantwortlich. Er sucht die von ihm ausführbaren Testfälle im Java-Programm und

lässt sie in einer eigenen Java Virtual Machine durch das JUnit-Framework ausführen. Das Ergeb-

nis des Testdurchlaufs wird dann an den aufrufenden Code weitergeleitet. Abschnitt 3.2.2 widmet

sich dem JUnit Test Runner.

Benutzungsoberfläche (de.feu.ps.refacola.mutation.ui)

Die Benutzungsoberfläche stellt den nach außen sichtbaren Teil des Mutation Testing Frameworks

als Eclipse-View dar. Sie gibt dem Entwickler die Möglichkeit Programm und Testbasis für das

Mutation Testing auszuwählen und stellt vor der Ausführung sicher, dass die Vorbedingungen er-

füllt sind. Ein Mutation Testing Durchlauf kann für größere Programme eine längere Zeit in An-

spruch nehmen. Die Mutation Testing View teilt dem Entwickler daher den Fortschritt kontinuier-

lich mit. Am Ende eines Mutation Testing Durchlaufs werden die Ergebnisse in der Eclipse-View

dargestellt und Mutanten können vom Entwickler in einem Eclipse-Editor aufgerufen werden. Ab-

schnitt 3.2.3 geht näher auf die Benutzungsoberfläche ein.

Page 29: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 23

3.2.1 Anwendungskern

Der Anwendungskern umfasst die gesamte Funktionalität zur Durchführung von Mutation Testing mit

Refacola abgesehen von der Ausführung von JUnit Tests. Da deren Ausführung nicht nur auf das Mu-

tation Testing begrenzt sein muss und ggf. für andere Refacola-Komponenten zukünftig relevant sein

kann, wurde diese Funktionalität in einem separaten Eclipse-Plugin, dem JUnit Test Runner, ausgela-

gert (s. Abschnitt 3.2.2). Zur Erfüllung seiner Aufgabe nutzt der Anwendungskern verschiedene Kom-

ponenten von Refacola.

algorithm mutationTesting(javaProgramm, testbasis)

precondition

javaProgramm darf keine ungespeicherten Änderungen beinhalten

javaProgramm muss kompilierbar sein

testbasis darf keine ungespeicherten Änderungen beinhalten

testbasis muss kompilierbar sein

testbasis darf keine Tests enthalten, die fehlschlagen

end precondition

kompiliere javaProgramm und testbasis

erzeuge Java-Faktenbasis für javaProgramm

initialisiere Kontext der Refaktorisierung

generiere Mutationen

foreach Mutation

suche Lösungen zur Mutation

if Lösung vorhanden then

ändere Programm zu Mutant

if Mutant kompilierbar then

führe testbasis auf Mutant aus

end if

ändere Mutant zu Programm

sammle Mutation und Ergebnis des Testdurchlaufs

end if

end foreach

gib gesammelte Mutationen und Ergebnisse zurück

end mutationTesting.

Algorithmus 2 Mutation Testing mit Vorbedingungen

Der Ablauf eines Mutation Testing Durchlaufs ist in Algorithmus 2 dargestellt. Auf einzelne Schritte

wird in den folgenden Abschnitten näher eingegangen. Zentraler Punkt des Anwendungskerns ist die

Klasse MutationService, welche den Algorithmus implementiert, und eine einfache Schnittstelle

für Programme, die den Anwendungskern verwenden möchten, zur Verfügung stellt.

Während der Ausführung eines Mutation Testing Durchlaufs werden Informationen über den Fort-

schritt an den aufrufenden Code weitergeleitet. Java stellt dazu das Interface IProgressMonitor zur

Verfügung, über welches der Fortschritt in Form von Arbeitseinheiten angegeben werden kann. Mit-

tels dieses Interfaces kann der aufrufende Code ebenfalls den Abbruch einer Operation anstoßen. Es

liegt in der Verantwortung der aufgerufenen Methode das Abbruch-Flag des IProgressMonitors

auszuwerten und entsprechend darauf zu reagieren. Im Falle des Anwendungskerns des Mutation Tes-

Page 30: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

24 3 Implementierung

ting Frameworks wird in kurzen, regelmäßigen Abständen das Abbruch-Flag geprüft, um möglichst

schnell auf die Anforderung eines Abbruchs reagieren zu können. Es wird dann gemäß Java-

Konvention eine OperationCanceledException geworfen. Diese Technik wird von der Benut-

zungsoberfläche des Mutation Testing Frameworks genutzt, um dem Entwickler eine Rückmeldung

über den Fortschritt des Mutation Testing zu geben. Gleichzeitig erhält er damit die Möglichkeit einen

Mutation Testing Durchlauf abzubrechen. Leider schleicht sich damit zusätzliche Funktionalität in den

Anwendungskern ein, was die Lesbarkeit des Codes mindert. Trotz dieses Nachteils überwiegen die

Vorteile, weshalb sich für eine Umsetzung entschieden wurde.

3.2.1.1 Vorbedingungen

Die ordnungsgemäße Ausführung des Mutation Testing erfordert die Einhaltung verschiedener Vorbe-

dingungen (s. Algorithmus 2). Sie werden nicht vom MutationService geprüft, die Schnittstelle

stellt aber Methoden zur Verfügung, damit der aufrufende Code diese vor Ausführung des Mutation

Testing selbst prüfen kann. Die Einhaltung der Vorbedingungen, welche kurz erläutert werden, werden

durch die Benutzungsoberfläche des Mutation Testing Frameworks sichergestellt.

Das Java-Programm darf keine ungespeicherten Änderungen beinhalten

Im Laufe des Mutation Testing wird das Java-Programm mehrfach geändert und kompiliert. Damit

transiente Änderungen am Programm während des Mutation Testing nicht verlorengehen, sollten

diese vorher entweder gespeichert oder verworfen werden.

Das Java-Programm muss kompilierbar sein

Das Java-Programm darf keine syntaktischen und semantischen Fehler im Sinne der Java Sprach-

spezifikation aufweisen. Da während des Mutation Testing nur die bindungserhaltenen Constraints

manipuliert werden, welche nicht dafür Sorge tragen, dass das Programm kompilierbar bliebt, wür-

de aus einem nicht kompilierbaren Programm nur nicht kompilierbare und damit ungültige Mutan-

ten generiert werden. Damit würde das Ziel des Mutation Testing, nämlich das Finden von nicht

getöteten, aber gültigen Mutanten verfehlt. Eine Ausführung unter diesen Bedingungen wäre somit

überflüssig.

Die Testbasis darf keine ungespeicherten Änderungen beinhalten

Wie beim Java-Programm soll diese Vorbedingung sicherstellen, dass nicht gespeicherte Änderun-

gen an der Testbasis nicht verlorengehen.

Die Testbasis muss kompilierbar sein

Entsprechend dem Java-Programm, aus dem Mutanten generiert werden sollen, muss das Java-

Programm, welches als Testbasis fungiert, kompilierbar sein. Andernfalls kann die Testbasis nicht

verwendet werden, um festzustellen, welche Mutanten getötet und welche nicht getötet werden.

Die Testbasis darf keine Tests enthalten, die fehlschlagen

Testbasen, die fehlschlagende Tests enthalten, würden beim Mutation Testing dazu führen, dass

Mutanten als getötet erkannt werden, obwohl der Grund für die fehlgeschlagenen Tests nicht unbe-

dingt auf die Änderungen am ursprünglichen Programm zurückzuführen ist. Zusätzlich könnte es

durchaus passieren, wenn auch nur in seltenen Fällen, dass ein Mutant im Gegensatz zum ursprüng-

Page 31: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 25

lichen Programm alle Tests erfolgreich durchläuft. Um eine verlässliche Ausgangssituation zu ha-

ben, darf kein Test fehlschlagen. Eine Testbasis, die keine JUnit Tests beinhaltet, erfüllt diese Vor-

bedingung automatisch. Damit verhält sich das Mutation Testing Framework analog zu JUnit.

3.2.1.2 Erzeugung der Java-Faktenbasis

Zu Beginn eines Mutation Testing Durchlaufs wird die Faktenbasis des Java-Programms durch Refa-

cola über einen IProgramInfoProvider erzeugt. Die Faktenbasis leitet sich aus dem abstrakten

Syntaxbaum (Abstract Syntax Tree, AST) ab, der von Eclipse aus einem Java-Programm erstellt wird.

Sie hält Informationen über die Programmelemente sowie deren Beziehungen untereinander und wird

benötigt, um das RefactoringProblem zu initialisieren. Im späteren Verlauf des Mutation Testing

Durchlaufs wird das Objekt, welches die Fakten erzeugt, noch benötigt, um aus den von Refacola er-

mittelten Änderungen Change-Objekte aufzubauen, mit dessen Hilfe Eclipse Änderungen am Java-

Programm vornehmen kann.

3.2.1.3 Initialisierung des Kontextes einer Refaktorisierung

Die Klasse RefactoringProblem stellt den Kontext einer Refaktorisierung dar. Sie verwaltet einer-

seits alle beteiligten Programmelemente und Fakten. Diese Informationen werden über einen

IProgramInfoProvider bereitgestellt, der schon im vorherigen Schritt für die Erzeugung der Fak-

tenbasis zuständig war. Sowohl die von ihm ermittelten Programmelemente als auch die Fakten wer-

den unverändert zur Initialisierung des RefactoringProblems verwendet. Für die Suche nach einer

Lösung eines Constraint-Systems ist ein Constraint-Solver zuständig. Zur Zeit wird in Refacola nur

der Choco genannte Constraint-Solver unterstützt, weshalb er auch beim Mutation Testing standard-

mäßig zum Einsatz kommt. Dem RefactoringProblem wird eine ISolverFactory injiziert, da-

mit es Zugriff auf den zur Lösung eines Constraint-System zu verwendenden ISolver hat. Anderer-

seits muss spezifiziert werden, welche Eigenschaften von Programmelementen sich im Laufe der Su-

che nach einer Lösung ändern dürfen oder sogar müssen. Soll beispielsweise ein Programmelement

umbenannt werden, muss seiner Identifier-Eigenschaft ein neuer Wert zugeweisen werden, welcher

dem neuen Namen des Programmelements entspricht. Bis zu diesem Punkt wird das

RefactoringProblem im Mutation Testing Framework genauso verwendet wie es auch von den in

Refacola spezifizierten Refaktorisierungen benutzt wird.

Im Gegensatz zu den Refaktorisierungen werden beim Mutation Testing keine bestimmten Werte für

ausgewählte Eigenschaften einiger Programmelementen gefordert. Die einzige Bedingung ist, dass

sich das Verhalten des Programms ändern soll, was implizit durch die Negierung eines bindungserhal-

tenen Constraints realisiert wird. Nun müssen aber Änderungen von Eigenschaften der Programmele-

mente erlaubt werden, sonst ist eine Lösung des Constraint-Systems nicht möglich. Der Grund besteht

darin, dass das ursprüngliche Constraint-System lösbar ist. Es wurde aus einem syntaktisch und se-

mantisch korrekten Programm erzeugt. Die Vorbedingungen des Mutation Testing stellen das sicher.

Wird jetzt ein Constraint negiert, kann dieses mit der ursprünglichen Variablenbelegung nicht mehr

erfüllt werden. Im Gegensatz zu Refaktorisierungen ist die konkrete Belegung der Werte von Eigen-

schaften der Programmelemente nicht von Bedeutung. Ob bei einem Mutanten eine Methode umbe-

nannt wurde, um seine syntaktische und semantische Korrektheit sicherzustellen oder nicht, ist nicht

relevant. Es geht schließlich nur darum ein Programm mit abweichenden Verhalten zu erhalten. Des-

Page 32: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

26 3 Implementierung

halb wird auch die Änderung beliebiger Eigenschaften von Programmelementen akzeptiert. Dies wird

dem über das RefactoringProblem erreichbaren ICodomainProvider mitgeteilt. Der

ICodomainProvider verwaltet alle Informationen über zu ändernde Eigenschaften von Programm-

elementen. Dass deren scheinbar willkürliche Belegung von Werten immer zu kompilierbaren Pro-

grammen führt, wird durch die syntaktischen und semantischen Constraints sichergestellt.

3.2.1.4 Generierung von Mutationen

Das Constraint-System wird nun einmalig für den Durchlauf des Mutation Testing aufgebaut. Dazu

werden erst einmal die Eigenschaften aller im RefactoringProblem verwalteten Programmelemen-

te gesammelt. Danach werden auf den gesammelten Eigenschaften der Programmelemente die in

Refacola definierten Constraint-Regeln angewendet, welche unter Verwendung des

RefactoringProblems Constraints erzeugen. Die Menge der erzeugten Constraints ergeben das

Constraint-System des Programms.

Zum Zeitpunkt der Entwicklung des Mutation Testing Frameworks standen zwei Implementierungen

zur Erstellung von Constraint-Systemen in Refacola zur Auswahl. Während der

CompleteConstraintSetGenerator alle Constraints erzeugt, generiert der

MinimizedConstraintSetGenerator ausgehend von einem RefactoringProblem nur so viele

Constraints, wie zur Lösung benötigt werden. Außerdem kann der

MinimizedConstraintSetGenerator die Constraint-Generierung vorzeitig abbrechen, wenn er

feststellt, dass das Constraint-System nicht lösbar ist. Da ein generischer Ansatz für das Mutation Tes-

ting verwendet wird und daher das RefactoringProblem nicht auf einige ausgewählte Programm-

elemente eingegrenzt wird, kommt nur der CompleteConstraintSetGenerator in Frage.

Leider konnte zur Generierung des Constraint-Systems keine der bereits in Refacola vorhandenen

Implementierungen verwendet werden, da nur während des Aufbaus des Constraint-Systems ein Zu-

sammenhang zwischen Constraint-Regeln und den daraus generierten Constraints hergestellt werden

kann. Ein Eingriff in den in Refacola implementierten Algorithmus ist von außen nicht möglich. Aus

diesem Grund fand eine Reimplementierung des Algorithmus zur Erzeugung eines Constraint-Systems

innerhalb des Mutation Testing Frameworks statt.

Durch die Reimplementierung wurde es erst möglich einen IProgressMonitor während der Erzeu-

gung des Constraint-Systems zu verwenden. Wie bereits am Anfang von Abschnitt 3.2.1 beschrieben,

wird er zur Fortschrittsanzeige innerhalb der Benutzungsoberfläche des Mutation Testing Frameworks

verwendet. Informationen über die aktuellen Arbeitsschritte der Constraint-System-Erzeugung können

so dem Entwickler präsentiert werden. Er hat darüber hinaus die Möglichkeit das Mutation Testing

abzubrechen.

Der Zusammenhang zwischen Constraint-Regeln und Constraints ist notwendig, da die Identifizierung

der zu negierenden Constraints über ihre Constraint-Regeln erfolgt. Diese sind, falls sie in Refacola

entsprechend ausgezeichnet wurden (s. Abschnitt 3.3), mit einer Negatable-Annotation versehen.

Trifft dies für eine Constraint-Regel zu, wird jedes von ihr erzeugte Constraint für die spätere Negie-

rung sowie die Constraint-Regel, aus der es hervorging, gesammelt. Nachdem alle Constraints erzeugt

wurden, werden für alle gesammelten Constraints ihre negierten Gegenstücke erstellt. Dazu wird eine

von Refacola bereitgestellte statische Fabrikmethode verwendet, welche aus einem übergebenen

Page 33: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 27

Constraint ein ComposedConstraint erstellt, das genau dann erfüllt ist, wenn das ursprüngliche

Constraint nicht erfüllt ist und umgekehrt.

Zum jetzigen Zeitpunkt existiert einerseits das Constraint-System. Andererseits wurden die

Constraints gesammelt, die negiert werden sollen. Zu diesen Constraints wurden die negierten

Constraints bereits erstellt. Die vorhandenen Objekte müssen nun so verknüpft werden, dass im nach-

folgenden Schritt eine Menge von Constraint-Systemen zur Verfügung steht, aus denen jeweils ein

Mutant erzeugt werden kann, falls der Constraint-Solver mindestens eine Lösung findet. Um einen

Bezug zwischen einzelnen Constraint-Paaren herzustellen, diese enthalten das ursprüngliche

Constraint sowie dessen Negation, werden diese jeweils in IConstraintChanges mit den entspre-

chenden Constraint-Regeln zusammengefasst (s. Abbildung 3). Die Constraint-Regeln werden zur

Darstellung innerhalb der Mutation Testing View verwendet. Jedes IConstraintChange wird wie-

derum in eine IMutation verpackt, die ihrerseits auf das zur Zeit einzige Constraint-System verweist.

Abbildung 3 Mutationen, Ausschnitt des Klassendiagramms

3.2.1.5 Suche nach Lösungen für Mutationen

Nachdem alle möglichen Mutationen (IMutation) aufgrund der durch die Annotation Negatable

ausgezeichneten Constraint-Regeln gesammelt wurden und das Constraint-System aufgebaut wurde,

werden nun Lösungen für die Mutationen gesucht. Dabei sind zwei Arten von Constraint-Systemen zu

unterscheiden.

IConstraint<<interface>>

IConstraintChange<<interface>>

+originalConstraint: IConstraint {ReadOnly}+changedConstraint: IConstraint {ReadOnly}+appliedRule: IRule {ReadOnly}

*2

IMutation<<interface>>

+originalConstraints: Set<IConstraint> {ReadOnly}+constraintChange: IConstraintChange {ReadOnly}+refactoringProblem: IRefactoringProblem {ReadOnly}

+mutateConstraints(): Set<IConstraint>

1

1

Set<IConstraint><<interface>>

*1

Constraint-System

Page 34: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

28 3 Implementierung

Ursprüngliches Constraint-System

Das ursprüngliche Constraint-System wird aus dem Java-Programm erzeugt, das für das Mutation

Testing verwendet wird. Das Constraint-System ist identisch mit dem Constraint-System, welches

von den in Refacola spezifizierten Refaktorisierungen aus demselben Java-Programm aufgebaut

wird.

Mutiertes Constraint-System

Ein mutiertes Constraint-System entsteht aus einem ursprünglichen Constraint-System, in welchem

ein Constraint durch seine Negation ersetzt wurde. Aus einer Lösung eines mutierten Constraint-

Systems kann ein Mutant erstellt werden.

Wie aus Abbildung 3 aus dem vorherigen Abschnitt ersichtlich ist, hält jede Mutation (IMutation)

eine Referenz auf ein Constraint-System. Es handelt sich dabei um das einzige ursprüngliche

Constraint-System, das während eines Mutation Testing Durchlaufs existiert. Zudem hat eine Mutati-

on Zugriff auf ein ConstraintChange, das ein ursprüngliches Constraint und seine negierte Form

verwaltet.

Abbildung 4 Objektdiagramm zum Zeitpunkt vor der Erzeugung eines mutierten Constraint-Systems

In Abbildung 4 ist ein Beispiel für das Objektgeflecht von Mutationen und Constraints abgebildet,

welches den Zustand nach der Generierung von Mutationen und vor der Erzeugung von mutierten

Constraint-Systemen verdeutlicht. Grundlage ist das Klassendiagramm aus Abbildung 3. Anstelle

konkreter Klassen werden zum Zwecke der besseren Übersicht Interfaces in der Abbildung verwendet.

Damit der Constraint-Solver Lösungen für eine Mutation suchen kann, muss ihm ein mutiertes

Constraint-System zur Verfügung stehen. Jede Mutation kann aus dem ursprünglichen Constraint-

System und einem ConstraintChange ein mutiertes Constraint-System erstellen. Dazu werden die

Referenzen auf alle ursprünglichen Constraints dupliziert und in einem eigenen Set verwaltet. Dieses

Set stellt eine Kopie des ursprünglichen Constraint-Systems dar. Aus dem Set wird die Referenz auf

das ursprüngliche Constraint, welches durch eine Mutation referenziert ist, entfernt und durch die Re-

ferenz des negierten Constraints ersetzt. Die Mutation hat damit ein mutiertes Constraint-Set erstellt,

zu dem der Constraint-Solver Lösungen suchen kann (s. Abbildung 5). Andere Mutationen werden

davon nicht beeinflusst, da nur lesend auf die gemeinsam genutzten Constraints zugegriffen wird. Die-

se Situation kann ausgenutzt werden, um das Mutation Testing Framework für eine parallele Verarbei-

: IMutation

Original : Set<IConstraint>

: IMutation

Original1 : IConstraint Negated1 : IConstraint

Original2 : IConstraint

Negated2 : IConstraint

: IConstraint : IConstraint : IConstraint

Page 35: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 29

tung anzupassen. In der jetzigen Implementierung findet ein Mutation Testing Durchlauf noch sequen-

ziell statt (s. Abschnitt 3.5).

Abbildung 5 Objektdiagramm zum Zeitpunkt nach der Erzeugung eines mutierten Constraint-Systems

Unter Verwendung der im RefactoringProblem gehaltenen ISolverFactory wird ein

Constraint-Solver erstellt, der nach Lösungen für das mutierte Constraint-System sucht. Aus einer

gefundenen Lösung werden die an den Eigenschaften der Programmelemente durchzuführenden Än-

derungen, die für die jeweilige Lösung nötig sind, als IChanges zu IChangeSets zusammengefasst,

die wiederum einem IRefactoringResult hinzugefügt werden. Dabei orientiert sich die Suche

einer Lösung mit Hilfe des Constraint-Solvers an dem in Refacola verwendeten Algorithmus. Konnte

keine Lösung gefunden werden, wird die Mutation verworfen. Die nachfolgenden Schritte werden für

diese dann nicht mehr ausgeführt. Die Berechnung vieler Lösungen ist sehr aufwändig und erhöht

sowohl die Laufzeit des Mutation Testing als auch den Speicherbedarf während der Lösungssuche

enorm.

Leider konnte auch hier die in Refacola bereits vorhandene Implementierung nicht verwendet werden,

da ihre Nutzung nur möglich ist, wenn von der AbstractRefactoring-Klasse geerbt wird. Wie

auch bei der Erzeugung des Constraint-Systems, brachte die Reimplementierung den Vorteil mit sich,

dass nun ein IProgressMonitor für die Fortschrittsanzeige eingesetzt werden kann. Außerdem hat

der aufrufende Code die Möglichkeit die Operation über den IProgressMonitor abzubrechen.

3.2.1.6 Änderung des ursprünglichen Programms zu einem Mutanten

Im Allgemeinen kann der Constraint-Solver mehrere Lösungen zu einem mutierten Constraint-System

finden, die sich durch die Werte der Eigenschaften von Programmelementen oder Manipulation ver-

schiedener Programmelemente unterscheiden. Für das Mutation Testing sind diese Lösungen unterei-

nander insoweit äquivalent, als dass sie Mutanten erzeugen, die paarweise ein identisches Verhalten

aufweisen. Beispielsweise kann eine Lösung dazu führen, dass eine Methode umbenannt wird, wäh-

: IMutation

Original : Set<IConstraint>

: IMutation

Original1 : IConstraint Negated1 : IConstraint

Original2 : IConstraint

Negated2 : IConstraint

: IConstraint : IConstraint : IConstraint

Mutated : Set<IConstraint>

Erzeugt

Page 36: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

30 3 Implementierung

rend bei einer andere Lösung der Methodenname nicht verändert wird. Im Gegensatz zu Refaktorisie-

rung sind diese Unterschiede für das Erkennen von Mutanten durch eine Testbasis nicht von Bedeu-

tung. Es könnte daher eine beliebige Lösung für ein mutiertes Constraint-System ausgewählt und die

Änderungen am Programm durchgeführt werden.

Als Auswahlkriterium für die vom Constraint-Solver gefundenen Lösungen wurde die Anzahl der

vorzunehmenden Änderungen mit dem Hintergrund gewählt, dass jede Änderung am Programm die

gleichen Laufzeitkosten verursacht. Es wird daher immer eine Lösung mit minimalen Änderungen aus

der Menge aller vom Constraint-Solver berechneten Lösungen eines mutierten Constraint-Systems

ausgewählt, um die Kosten für Änderungen am Programm zu minimieren.

Eine Lösung wird in Refacola durch ein IChangeSet repräsentiert, welches seinerseits eine Menge

von IChanges hält, die jeweils Änderungen an Eigenschaften von Programmelementen darstellen. In

Eclipse werden Änderungen allerdings durch Changes definiert. Da die Rückschreibekomponente von

Eclipse verwendet werden soll, diese stellt auch die Möglichkeit zur Verfügung Änderungen wieder

rückgängig zu machen, müssen die in den IChanges gehaltenen Informationen in Changes überführt

werden. Diese Aufgabe wird durch eine weitere Komponente von Refacola, dem Manipulator,

übernommen. Für in Refacola spezifizierte Refaktorisierungen wird dieselbe Vorgehensweise verwen-

det. Der IProgramInfoProvider aus Abschnitt 3.2.1.2 bietet eine einfache Möglichkeit unter Nut-

zung des Manipulators aus IChangeSets Changes zu erstellen.

In dem Mutation Testing Framework kapselt ein ICodeRewriter die Komplexität der in den vorhe-

rigen Abschnitten dargestellten Vorgehensweise. Er ist damit in der Lage Änderungen am Programm

in Form von Changes von Eclipse durchführen zu lassen. Dabei profitiert er von der Möglichkeit, dass

Eclipse zu in Changes gekapselten Änderungen Undo-Changes berechnen kann, welche die Änderun-

gen, die aus dem ursprünglichen Programm einen Mutanten entstehen lassen, wieder rückgängig ma-

chen. Wie in Abschnitt 3.1 erläutert, ist dies wichtig, damit der Entwickler beliebig zwischen Pro-

gramm und Mutanten hin- und herwechseln kann. Ferner ist für den Algorithmus 2 aus Abschnitt 3.2.1

das Zurücksetzen der Änderungen nötig, damit nach der Ausführung der Testbasis der nächste Mutant

aus dem ursprünglichen Programm erzeugt werden kann.

Welche Änderungen durch eine Mutation erfolgt sind und worin sich ein Mutant vom ursprünglichen

Programm unterscheidet, wird im Eclipse-Editor durch Hervorhebung der entsprechenden Programm-

elemente deutlich. Das erleitert den Vergleich zwischen Programm und Mutant. Da der

ICodeRewriter bereits über Informationen bezüglich der Änderungen verfügt, kann er aus Changes

die Codestellen herausfiltern, die davon betroffen sind. Über die genaue Position der Änderungen in

einzelnen CompilationUnits, diese entsprechen in Eclipse den Java-Dateien eines Programms,

werden die Programmelemente herausgefiltert.

3.2.1.7 Ausführung der Testbasis und Bestimmung des Ergebnisses

Nachdem aus dem ursprünglichen Programm ein Mutant generiert wurde, wird dieser jetzt kompiliert.

Schlägt dies fehl, so ist der Mutant im Sinne der Sprachspezifikation fehlerhaft und damit nicht aus-

führbar. Unter der Voraussetzung, dass die in Refacola vorhandene Sprachdefinition vollständig und

korrekt ist, bedeutet dies, dass ein Constraint negiert wurde, welches die syntaktische und semantische

Korrektheit des Programms sicherstellt. Der Mutant wird als ungültig gekennzeichnet. Zur Verbesse-

Page 37: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 31

rung der Ergebnisse eines Mutation Testing Durchlaufs sollten möglichst keine ungültigen Mutanten

generiert werden (s. Abschnitt 3.3).

War die Kompilierung des Mutanten erfolgreich, dann wird die Ausführung der Testbasis mittels des

JUnit Test Runners (s. Abschnitt 3.2.2) angestoßen. Dieser führt alle im übergebenen Java-Programm

vorhandenen JUnit-Tests aus, die auf JUnit 3.8.x oder JUnit 4 basieren. Nach Abschluss der Tests

wird das Result entgegengenommen und ausgewertet. Der Mutant wurde genau dann von der Testba-

sis erkannt und gilt als getötet, wenn mindestens ein Test fehlschlägt.

Folgende Ergebnisse sind für einen Mutanten möglich.

Der Mutant wurde getötet

Der Mutant weist ein dem ursprünglichen Programm abweichendes Verhalten auf, welches von au-

ßen sichtbar ist und durch die Testbasis entdeckt wurde. Mindestens ein Test ist fehlgeschlagen.

Der Mutant wurde nicht getötet

Der Mutant wurde von der Testbasis nicht entdeckt. Keiner der Tests ist fehlgeschlagen. Es kann

nicht bestimmt werden, ob sich der Mutant anders verhält als das ursprüngliche Programm. Es wird

empfohlen, dass der Entwickler den Mutanten dahingehend genauer zu untersuchen. Falls es sich

nicht um einen äquivalenten Mutanten handelt, kann er durch Einführung weiterer Tests erkannt

werden. Dadurch wird auch die Testabdeckung erhöht. Testbasen, die keine Tests enthalten, kön-

nen keine Mutanten entdecken, weil nie ein Test fehlschlagen kann.

Der Mutant ist ungültig

Der Mutant konnte nicht kompiliert werden, da er fehlerhaft im Sinne der Sprachspezifikation ist.

Es kann sein, dass ein Constraint negiert wurde, welches die syntaktische und semantische Kor-

rektheit eines Programms sicherstellt. Es wird empfohlen zu prüfen, ob die Auszeichnung der

Constraint-Regel, welche das Constraint erzeugt hat, überhaupt zu gültigen Mutanten führen kann

(s. auch Abschnitt 3.3). Die betreffende Constraint-Regel kann über die Mutation Testing View be-

stimmt werden.

Die Testbasis selbst sollte nur über solche Tests verfügen, die nicht manuell konfiguriert werden müs-

sen, da ein Eingreifen seitens des Entwicklers während des Mutation Testing nicht möglich ist (vom

Abbruch der Operation einmal abgesehen). Da die Testbasis für jeden gültigen Mutanten einmal kom-

plett ausgeführt wird, können lang laufende Tests die Laufzeit des Mutation Testing stark beeinflus-

sen. Testbasen, die für das Mutation Testing verwendet werden, sollten ausschließlich Unit-Tests und

schnell ausführbare, sich selbst konfigurierbare Integration-Tests beinhalten.

3.2.2 JUnit Test Runner

Der JUnit Test Runner ist verantwortlich für die Ausführung von JUnit Tests eines Java-Programms.

Er wird während des Mutation Testing eingesetzt, um zu bestimmen, ob der generierte Mutant von der

Testbasis erkannt wird oder nicht. Der JUnit Test Runner ist allgemein genug, um ihn auch in einem

anderen Kontext abseits des Mutation Testing zu verwenden, wo Resultate eines Testdurchlaufs benö-

tigt werden. Die Auswertung der Resultate obliegt dem aufrufenden Code. Damit eine Wiederverwen-

dung des JUnit Test Runners möglich ist und Details der Testausführung nicht im Anwendungskern

Page 38: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

32 3 Implementierung

des Mutation Testing untergehen, wurde er von diesem separiert und in ein eigenes Eclipse-Plugin

untergebracht.

Zur Ausführung einer Testbasis wird dem JUnit Test Runner ein Java-Programm übergeben, welches

die auszuführenden JUnit Tests beinhaltet. JUnit bietet mit JUnitCore eine einfache Schnittstelle an,

um Tests, welche in den zu übergebenen Klassen (Objekte vom Typ Class) vorhanden sind, auszu-

führen und liefert ein Ergebnis des Testdurchlaufs, das u. a. Informationen über die Anzahl der fehlge-

schlagenen Tests bereitstellt. Genau diese Information ist für das Mutation Testing von entscheidender

Bedeutung, um festzustellen, ob ein Mutant von der verwendeten Testbasis erkannt wurde. Eclipse

stellt ebenfalls eine Klasse JUnitCore zur Verfügung, die nicht mit der o. g. Klasse aus dem JUnit-

Framework verwechselt werden sollte. Mit dessen Hilfe kann aus einem IJavaElement, ein Java-

Programm stellt in Eclipse ebenfalls ein IJavaElement dar, alle Klassen ermittelt werden, die JUnit

Tests beinhalten. Diese Klassen können dann als Eingabe für JUnitCore aus dem JUnit-Framework

verwendet werden. Die in ihnen enthaltenen JUnit Tests werden dann ausgeführt, sofern sie auf JUnit

3.8.x oder JUnit 4 basieren.

Für jeden Testdurchlauf wird lokal auf dem Rechner eine Java Virtual Machine gestartet, in denen die

JUnit Tests mittels JUnit ausgeführt werden. Über Eclipse kann die für ein Java-Programm benötigte

Version einer Java Virtual Machine bestimmt werden. Zusätzlich benötigt die neue Instanz der Java

Virtual Machine Informationen über die zu verwendenden Klassenpfade, damit die Klassen, welche

die JUnit Tests beinhalten, von ihr gefunden werden können. Gleiches gilt für die Bibliotheken des

JUnit Frameworks. Beides übergibt der JUnit Test Runner an die Java Virtual Machine.

Abbildung 6 Abstrakter Programmfluss zwischen Client und Server

Der jetzt folgende Ablauf orientiert sich an dem in Abbildung 6 dargestellten Sequenzdiagramm. Da-

bei wurde von der konkreten Implementierung abstrahiert, um sich auf das Wesentliche zu beschrän-

ken. Der IJUnitClient, im folgenden Client genannt, konfiguriert und startet eine Instanz der Java

Virtual Machine (IVMRunner). Er wird intern vom IJUnitRunner verwendet, der wiederum die

öffentliche Schnittstelle des JUnit Test Runners darstellt. Der JUnitServer, nachfolgend Server

genannt, ist die Klasse, welche von der neuen Instanz der Java Virtual Machine ausgeführt wird. Als

Kommunikationskanal zwischen dem Client und dem Server wird ein TCP-Socket verwendet. Der

Client wählt vor dem Starten der Java Virtual Machine Instanz einen freien Port aus, über den dann

: IJUnitClient : JUnitServer : IVMRunner

1 : run()

2 : run() 3 : main()

<<create>>

4 : openSocket() 5 : runTests()

6 : connectSocket()

7 : testResult

Page 39: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 33

eine Verbindung zwischen Client und Server aufgebaut wird. Der Server benötigt einerseits Verbin-

dungsdetails zur Herstellung einer Verbindung (den Hostname und den Port), sowie andererseits die

Klassen, welche JUnit Tests beinhalten, die vom JUnit-Framework ausgeführt werden sollen. Alle

Informationen übergibt der Client an die gestartete Instanz der Java Virtual Machine, die sie als Para-

meter an das Server-Programm bei der Ausführung der bei Java-Programmen üblichen main-Methode

weiterleitet. Die Klassen (Class-Objekte) können nicht direkt übergeben werden, da nur String-

Parameter erlaubt sind. Stattdessen werden die vollqualifizierten Namen der Klassen an den Server

übergeben, aus denen die Java Laufzeitumgebung über den Klassenpfad Class-Objekte erstellen

kann. Während der Server die JUnit Tests ausführt, öffnet der Client einen TCP-Socket und wartet auf

das Resultat des Testdurchlaufs, welches vom JUnit-Framework als Result geliefert wird. Da das

von JUnit gelieferte Testergebnis-Objekt nicht serialisierbar ist, wurde es durch eine eigene serialisier-

bare Klasse im JUnit Test Runner ersetzt. Der Inhalt des JUnit Testresultats wird einfach in das hinzu-

gefügte Datentransferobjekt (Data Transfer Object, DTO) kopiert. Dies vereinfacht die Übertragung

des Testresultats vom Server zum Client, da auf ein eigenes Protokoll zum Datenaustausch verzichtet

werden kann. Mit Beendigung der main-Methode des Servers beendet sich auch die vom JUnit Test

Runner gestartete Java Virtual Machine. Das Testresultat wird anschließend an den aufrufenden Code

als Ergebnis zurückgegeben.

3.2.3 Benutzungsoberfläche

Die Benutzungsoberfläche des Mutation Testing Frameworks (s. Abbildung 7) ist als View in Eclipse

integriert. Der Aufbau orientiert sich stellenweise an den Type Constraints Tester [Bär10]. Standard-

mäßig wird sie nicht angezeigt, kann aber über die View-Auswahl von Eclipse aufgerufen werden. Sie

ist dort in der Kategorie General als Mutation Testing gekennzeichnet.

Es wurde Wert darauf gelegt, dass die Mutation Testing View harmonisch ins Bild der Entwicklungs-

umgebung Eclipse passt und einfach zu bedienen ist, sowie über genügend Funktionalität verfügt, um

dem Entwickler bei der Arbeit zu unterstützen. Dabei wurde bewusst auf die Verwendung von moda-

len Dialogen verzichtet, die den Entwickler bei seiner Arbeit unterbrechen könnten.

Abschnitt 3.2.3.1 befasst sich mit den Möglichkeiten, die von der View zur Verfügung gestellt wer-

den. Es wird auf einzelne Funktionen und Besonderheiten eingegangen. Darunter fallen auch die Vor-

bedingungen, die erfüllt sein müssen, damit Mutation Testing für ein Programm ausgeführt werden

kann. Abseits der View aus Abbildung 7 nutzt das Mutation Testing Framework die Statusleiste und

die Fortschrittsanzeige von Eclipse, deren Verwendung im Rahmen der View kurz beschrieben wird.

Die Implementierung der View unter Verwendung des Model View Presenter – ViewModel Ent-

wurfsmuster ist Thema von Abschnitt 3.2.3.2. Ihre Verwendung entkoppelt die Präsentationslogik und

den Zustand einer Oberfläche von ihrer Darstellung. Dazu nutzt sie das in Eclipse enthaltene

Databinding-Framework.

Während der Entwicklung der View traten Darstellungsfehler bei der Nutzung von transparenten Bil-

dern in Verbindung mit dem Tabellen-Widget aus dem SWT-Framework auf. Es stellte sich heraus,

dass es sich um einen Bug im SWT-Framework handelt. In Abschnitt 3.2.3.3 werden das Problem und

der verwendete Workaround näher erläutert.

Page 40: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

34 3 Implementierung

3.2.3.1 Aufbau und Inhalt

Abbildung 7 Mutation Testing View

Die Mutation Testing View teilt sich im Wesentlichen in einen Eingabe- und einen Ausgabeteil auf.

Der Eingabeteil umfasst zwei Comboboxen, in denen sowohl das Java Projekt ausgewählt werden

kann, aus dem Mutanten generiert werden, als auch die Testbasis, welche zur Erkennung dieser Mu-

tanten eingesetzt wird. Kann ein Mutation Testing Durchlauf nicht gestartet werden, wird dies durch

ein Symbol zwischen Beschriftung und Combobox angezeigt. Die Tabelle mit den Resultaten eines

Mutation Testing Durchlaufs stellt den Ausgabeteil dar. Operationen können über die Schaltflächen in

der Werkzeugleiste rechts neben den Reitern der View ausgeführt werden. Die Werkzeugleiste enthält

immer die Schaltflächen der gerade aufgerufenen View. Einige Operationen stehen auch im Kontext-

menü der Tabelle zur Verfügung.

Informationen über den letzten Mutation Testing Durchlauf werden in der Statusleiste von Eclipse

angezeigt, wenn die Mutation Testing View den Eingabefokus hält. Darüber hinaus wird während der

Ausführung von Mutation Testing Gebrauch von der Fortschrittsanzeige und der in Eclipse enthalte-

nen Progress View gemacht, um den Entwickler über laufende Aktivitäten in Kenntnis zu setzen.

Die einzelnen Elemente der Mutation Testing View und ihre Bedeutungen sowie eventuelle Besonder-

heiten werden im folgenden erläutert. Weiterführende Informationen zu den Vorbedingungen, die vor

Ausführung des Mutation Testing erfüllt sein müssen, finden sich in Abschnitt 3.2.1.1.

Combobox "Java Project"

In der Combobox "Java Project" stehen alle nicht geschlossenen Projekte des aktuell verwendeten

Workspace, die Java Quellcode beinhalten, aufsteigend nach Namen sortiert zur Auswahl. Aus dem

ausgewählten Java Projekt werden während des Mutation Testing Mutanten generiert. Ein

IResourceChangeListener überwacht Änderungen an den Projekten des Workspace, um die

Liste der zur Auswahl stehenden Java Projekte aktuell zu halten. Nach Auswahl eines Java Projekts

wird dieses auch automatisch als Testbasis vorausgewählt, sodass in Situationen, in denen das Java

Projekt ebenfalls die für das Mutation Testing zu verwendenden JUnit Tests zur Verfügung stellt,

eine nochmalige Auswahl desselben Projekts in der Combobox "Test Base" entfällt. Natürlich kann

auch ein anderes Java Projekt als Testbasis eingestellt werden. Erfüllt das ausgewählte Java Projek-

Page 41: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 35

te zum Zeitpunkt des Startens eines Mutation Testing Durchlaufs nicht alle Vorbedingungen, weist

ein dann eingeblendetes Symbol zwischen Beschriftung und Combobox mit einer der nachfolgen-

den Meldungen über die verletzte Vorbedingung hin.

"[Java Project]" has unsaved changes

"[Java Project]" contains errors

Combobox "Test Base"

Die Combobox "Test Base" listet ebenfalls alle nicht geschlossenen Projekte der Workspace auf,

sofern diese Java Quellcode enthalten. Der Synchronisierungsmechanismus der zur Auswahl ste-

henden Java Projekte basiert wie bei der Combobox "Java Project" auf einem

IResourceChangeListener. Das Enthalten sein von JUnit Tests wird in den zur Auswahl ste-

henden Java Projekten nicht überprüft. Ein Java Projekt ohne JUnit Tests wird beim Mutation Tes-

ting wie eine Testbasis betrachtet, die keine fehlschlagenden Tests enthält. So bekommt der Ent-

wickler auf einfache Art und Weise die Möglichkeit auch ohne vorhandene JUnit Tests einige

Kenntnisse aus den generierten Mutanten zu gewinnen. Die JUnit Tests in dem auswählten Java

Projekt werden während des Mutation Testing zur Erkennung von Mutanten verwendet. Verletzun-

gen der Vorbedingungen aus Abschnitt 3.2.1.1 resultieren in eine der folgenden Fehlermeldungen.

"[Test Base]" has unsaved changes

"[Test Base]" contains errors

"[Test Base]" contains failing JUnit Tests

Tabelle mit Mutation Testing Ergebnissen

In der Tabelle werden nach einem Mutation Testing Durchlauf die Ergebnisse zu allen generierten

Mutanten aufgelistet. Dazu gehört ebenfalls das ursprüngliche Constraint, dessen Negierung den

Mutanten hervorgebracht hat, sowie die Constraint-Regel, die für das Erzeugen des Constraints zu-

ständig war. Per Doppelklick auf eine Zeile wird der Mutant in einem Eclipse-Editor geöffnet und

das manipulierte Programmelement hervorgehoben. Das Kontextmenü der Tabelle enthält die

gleichnamigen in der Werkzeugleiste befindlichen Operationen zum Öffnen des ursprünglichen

Programms (Open Original in Editor) und des Mutanten (Open Mutant in Editor).

Mutant Result

Das Ergebnis der Auswertung eines Mutanten entspricht einem der folgenden Werte.

Mutant Survived

Der Mutant wurde nicht durch die Testbasis entdeckt.

Entweder ist sein von außen beobachtbares Verhalten äquivalent zu dem des ur-

sprünglichen Programms, dann kann er von keinem JUnit Test entdeckt werden,

oder die Testabdeckung ist nicht ausreichend, um ihn zu entdecken. Was von bei-

den zutrifft, kann der Entwickler durch einen Vergleich des Mutanten mit dem ur-

sprünglichen Programm herausfinden.

Page 42: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

36 3 Implementierung

Mutant Invalid

Der Mutant erfüllt nicht die Sprachspezifikation der Programmiersprache und ist

deshalb nicht kompilierbar.

Mutant Killed

Der Mutant weist ein dem ursprünglichen Programm abweichendes Verhalten auf,

das durch mindestens einen fehlgeschlagenen Test in der Testbasis entdeckt wurde.

Original Constraint

Das ursprüngliche Constraint, welches negiert wurde, um den Mutant zu generieren, wird

in dieser Spalte aufgelistet.

Constraint Rule

Die Spalte enthält die Constraint-Regel, welche das ursprüngliche Constraint erzeugt hat.

Sie ist in Refacola mit der Negatable-Annotation für das Mutation Testing ausgezeichnet

(s. Abschnitt 3.3).

Werkzeugleiste der Mutation Testing View

Der Inhalt der Werkzeugleiste wird durch die zur Zeit ausgewählte View bestimmt. Die der Muta-

tion Testing View zur Verfügung stehenden Schaltflächen in der Werkzeugleiste sind nach Ver-

wendungsart gruppiert und abhängig vom Zustand der View aktiviert oder deaktiviert.

Run

Startet einen Mutation Testing Durchlauf, in welchem aus dem unter "Java Project" ausge-

wählten Java Projekt Mutanten generiert werden. Das unter "Test Base" ausgewählte Java

Projekt dient als Testbasis. Sind die Vorbedingungen nicht erfüllt (s. Abschnitt 3.2.1.1),

wird eine Fehlermeldung ausgegeben. Zum Starten muss sowohl unter "Java Project" als

auch unter "Test Base" ein Java Projekt ausgewählt sein.

Cancel

Bricht einen laufenden Mutation Testing Durchlauf ab.

Open Original in Editor

Öffnet das ursprüngliche Programm zum in der Tabelle markierten Resultat in einem Ec-

lipse-Editor. Das Programmelement, welches durch die Mutation manipuliert wird, wird im

Editor hervorgehoben. Diese Operation steht auch im Kontextmenü der Tabelle zur Verfü-

gung.

Open Mutant in Editor

Öffnet den Mutant zum in der Tabelle markierten Resultat in einem Eclipse-Editor. Das

manipulierte Programmelement wird im Editor hervorgehoben. Diese Operation steht auch

im Kontextmenü der Tabelle zur Verfügung.

Show Killed Mutants

Zeigt die getöteten Mutanten in der Tabelle an, falls ausgewählt, andernfalls werden sie

herausgefiltert.

Page 43: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 37

Show Survived Mutants

Zeigt die nicht getöteten Mutanten in der Tabelle an, falls ausgewählt, andernfalls werden

sie herausgefiltert.

Show Invalid Mutants

Zeigt die ungültigen Mutanten in der Tabelle an, falls ausgewählt, andernfalls werden sie

herausgefiltert.

Benachrichtigungen über den Fortschritt während des Mutation Testing

Das Mutation Testing Framework verwendet an mehreren Stellen einen IProgressMonitor, um

dem aufrufenden Code Informationen über die laufenden Operationen eines Mutation Testing

Durchlaufs mitzuteilen. Ein solches Objekt wird von Eclipse während der Ausführung eines Jobs

zur Verfügung gestellt. Ein Job führt den in seiner run-Methode enthaltenen Code außerhalb des

GUI-Threads aus, damit die Benutzungsoberfläche weiterhin auf Aktionen des Benutzers reagieren

und sich bei Bedarf aktualisieren kann. Das Mutation Testing wird innerhalb eines Jobs ausge-

führt. Durch die Nutzungen eines Jobs und dem von ihm bereitgestellten IProgressMonitor ist

es möglich, dass Eclipse dem Entwickler den Fortschritt eines Mutation Testing Durchlaufs über

ein separates Fenster (s. Abbildung 8), der Progress View und der Fortschrittsanzeige in der Status-

leiste mitteilen kann.

Abbildung 8 Fortschrittsanzeige während des Mutation Testing

Informationen über den letzten Mutation Testing Durchlauf

In der Statusleiste von Eclipse werden Informationen (s. Abbildung 9) über den letzten Mutation

Testing Durchlauf angezeigt, wenn die View den Eingabefokus hält. U. a. sind der Name des Java

Projekts, aus dem Mutanten generiert wurden, der Name der Testbasis (in eckigen Klammern) und

der Zeitpunkt, an dem ein Durchlauf abgeschlossen wurde, enthalten.

Abbildung 9 Informationen über den letzten Mutation Testing Durchlauf

Page 44: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

38 3 Implementierung

3.2.3.2 Verwendung des Model View Presenter – ViewModel Entwurfsmusters

Das Model View Presenter – ViewModel (MVP-VM) Entwurfsmuster (s. Abbildung 10) ist aus dem

Model View ViewModel (MVVM) Entwurfsmuster entstanden, das sich bei der Entwicklung von

Windows Presentation Foundation (WPF) und Silverlight Anwendungen unter .NET zum Quasi-

Standard etabliert hat. Das wesentliche Merkmal beider Entwurfsmuster ist die Verwendung eines

Data Binding Frameworks zur Synchronisierung von Änderungen zwischen View und ViewModel,

das auch als PresenterModel bekannt ist. Steuerelemente innerhalb der View werden über das Data

Binding Framework an Eigenschaften eines ViewModels gebunden. Modifizierungen an den in der

View dargestellten Daten werden direkt an das zugeordnete ViewModel weitergereicht. Bei Änderun-

gen von Werten einer Eigenschaft des ViewModels wird das Data Binding Framework indirekt über

das manuelle Auslösen von Events davon unterrichtet, damit es den neuen Wert auslesen und an die

View weiterleiten kann. Das Model wird vor der View durch das ViewModel verborgen, welches als

Vermittler zwischen beiden dient. Auf Code zur Synchronisierung kann dann verzichtet werden. Die

lose Kopplung und die Verlagerung der Zustandsverwaltung von der View ins ViewModel erleichtern

das automatisierte Testen der Präsentationslogik. Im Gegensatz zum MVVM wird beim MVP-VM die

Präsentationslogik aus dem ViewModel in einen Presenter verschoben. Die View delegiert Aufgaben

durch den Aufruf entsprechender Methoden an den Presenter und reduziert jegliche Logik aufs Mini-

mum. Der Presenter hat zusätzlich die Möglichkeit wie der Presenter beim Model View Presenter

(MVP) Entwurfsmuster über ein von der View implementiertes Interface direkt mit ihr zu kommuni-

zieren.

Abbildung 10 MVP-VM Entwurfsmuster [Ezr09]

Da Eclipse ein Data Binding Framework zur Verfügung stellt, hat sich die Verwendung von MVP-VM

für das Mutation Testing Framework angeboten. Abbildung 11 stellt die Umsetzung von MVP-VM im

Mutation Testing Framework sowie die Abhängigkeiten zwischen den Bestandteilen des Entwurfs-

musters auf Ebene von Interfaces dar. Den in der Benutzungsoberfläche verwendeten Interfaces und

Page 45: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 39

Klassen sind jeweils nach den Bestandteilen von MVP-VM Suffixe angehängt. Ein IMutationPre-

senter entspricht damit einem Presenter im MVP-VM. Das Model ist nicht Bestandteil der Benut-

zungsoberfläche und umfasst im Falle des Mutation Testing Frameworks den Anwendungskern und

den JUnit Test Runner.

Nachfolgend wird das Zusammenspiel zwischen den Bestandteilen sowie ihre Zuständigkeiten be-

schrieben.

Abbildung 11 MVP-VM im Mutation Testing Framework

View

Die Benutzungsoberfläche besteht nur aus einer einzigen View (IMutationView), die genau der

Eclipse-View des Mutation Testing Frameworks entspricht. Sie nimmt Eingaben des Entwicklers

entgegen und leitet sie an ihren Presenter weiter, wenn es sich um Operationen handelt, die den

Anwendungskern betreffen. Werte von gebundene Steuerelemente, wie z. B. die ausgewählte Test-

basis, werden bei Änderungen automatisch durch das Data Binding mit dem im DataContext

enthaltenen ViewModel synchronisiert. Die Bindung zwischen Steuerelementen und Eigenschaften

des ViewModels wird durch die View aufgebaut, nachdem sie während der Initialisierung ihr

ViewModel durch den von ihr erzeugten Presenter zugeteilt bekommt. Andere Funktionalitäten,

welche nur die Darstellung der Steuerelemente beeinflussen, wie das Einfärben von Zeilen der Ta-

belle, oder Abhängigkeiten zu einem GUI Framework wie SWT oder JFace besitzen, sind weder

Bestand des Presenters noch des ViewModels und werden nur innerhalb der View verwendet. Da-

ten, die die View über das ViewModel erhält, wurden bereits für die Darstellung aufbereitet. Die

Konvertierung von Daten zwischen View und Model wird vom ViewModel übernommen. Kom-

plexe Daten, wie die in der Tabelle dargestellten Resultate, werden durch Aggregationen zwischen

ViewModels aufgelöst. Die Datenquelle der Tabelle ist eine Liste von ISolutionViewModels,

von denen jedes Element eine Zeile repräsentiert und Eigenschaften zum Abrufen der in den Spal-

IMutationView<<interface>>

IMutationPresenter<<interface>>

IMutationViewModel<<interface>>

IJavaProjectViewModel<<interface>>

ISolutionViewModel<<interface>>

1

1

1*

* *

*

*

1

*

IMutationSolution<<interface>>

*

1

IJavaProject<<interface>>

*

1

IJavaProjectWatcher<<interface>>

*

1

IMutationSolutionCompilation<<interface>>

*

0..1

IMutationService<<interface>>

Data Binding

Data Binding

Data Binding

IView<T extends IViewModel><<interface>>

IViewModel<<interface>>

Page 46: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

40 3 Implementierung

ten dargestellten Werte zur Verfügung stellt. Ähnliches gilt auch für die Daten in den Combobo-

xen.

Presenter

In der Regel existiert zu jeder View genau ein Presenter, der ihre Präsentationslogik implementiert

und Aufrufe an den Anwendungskern durchführt. Die einzige View des Mutation Testing Frame-

works erzeugt ihren eigenen Presenter (IMutationPresenter) und stellt ihm eine Referenz auf

sich selbst zur Verfügung, über die der Presenter der View ihr ViewModel übergeben kann. Der

Presenter ruft Methoden der View über ihr Interface auf, wenn der Entwickler Mutation Testing

ausführen möchte, Vorbedingungen aber nicht erfüllt sind. Zur Anzeige einer Fehlermeldung wird

eine ControlDecoration eingesetzt. Sie stellt keine Eigenschaft zur Verfügung, über die sie

wahlweise ein- und ausgeblendet werden kann. Dafür ist ein Aufruf der Methoden show() und

hide() nötig. Data Binding ist aber nur für Eigenschaften möglich. Deshalb erfolgt die Interaktion

mit den beiden verwendeten ControlDecorations über den Aufruf ihrer Methoden aus der View

heraus. Die Validierung der Eingabe bezüglich Einhaltung der Vorbedingungen übernimmt der

Presenter. Im negativen Fall teilt er der View über ihr Interface die darzustellende Fehlermeldung

mit. Die Logik zur Validierung und die Auswahl der Fehlermeldung konnte durch den Einsatz ei-

nes Presenter in Verbindung mit einem von der View abstrahierenden Interface trotz des Verzichts

auf Data Binding aus der View herausgehalten werden. Der Presenter kommuniziert ebenfalls mit

dem ViewModel und übergibt ihr nach dem Mutation Testing die Resultate. Damit während der

Ausführung des Mutation Testing ein erneuter Start nicht möglich ist, wird die Schaltfläche zum

Starten zeitweise deaktiviert. Der Presenter veranlasst dies zu Beginn der Ausführung über die Än-

derung des Wertes einer Eigenschaft des ViewModels. Die Änderung wird dem Data Binding Fra-

mework indirekt über Events mitgeteilt, sodass die Schaltfläche in der View deaktiviert wird.

ViewModel

Das ViewModel fungiert als Vermittler zwischen View und Model. Es stellt Eigenschaften zur

Verfügung, die an Steuerelemente über ein Data Binding Framework gebunden werden können.

Falls nötig werden Daten vom ViewModel für die Darstellung aufbereitet, wie es beim Mutation

Testing Framework für die Ausgabe von Nachrichten auf der Statusleiste von Eclipse der Fall ist.

Da die View auch komplexe (nicht elementare) Daten darstellt, werden verschiedene Typen von

ViewModels verwendet zwischen denen Aggregationsbeziehungen bestehen. Das

IMutationViewModel wird der einzigen View zugeordnet. Jedes Java Projekt, das in den Com-

boboxen gelistet ist, wird durch ein IJavaProjectViewModel repräsentiert. Wie beim Presenter

bereits angesprochen, erhält das ViewModel der View die Resultate des letzten Mutation Testing.

Es erstellt für jedes Resultat (IMutationSolution) ein ISolutionViewModel, in das jeweils

ein Resultat-Objekt injiziert wird, und stellt eine Liste dieser ViewModels fürs Data Binding über

eine Eigenschaft zur Verfügung. Damit das Data Binding Framework bei Änderungen von Werten

eines ViewModel die neuen Werte an gebundene Steuerelemente weiterleiten kann, muss ihm dies

über das Auslösen eines PropertyChange-Events mitgeteilt werden. Dazu muss das ViewModel

die Möglichkeit anbieten, dass sich bei ihm Komponenten des Data Binding Frameworks als

PropertyChangeListener anmelden können. Anmeldung und Verwaltung von

PropertyChangeListener sowie Methoden zum Auslösen von PropertyChange-Events wur-

den in eine abstrakte Klasse ausgelagert, die von allen ViewModels erweitert wird. Die Verwen-

dung der PropertyChange-Events und die Erstellung von Bindungen zwischen Steuerelementen

und Eigenschaften von ViewModels für das Data Binding haben den Nachteil, dass der Name der

Eigenschaft als String angeben werden muss. Das erschwert Refaktorisierungen, da z. B. die

Page 47: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 41

Umbenennung von Eigenschaften der ViewModels Strings nicht berücksichtigt. Es ist zusätzlich

eine manuelle Anpassung an allen Stellen nötig, wo der Name der umbenannten Eigenschaft ver-

wendet wird. Im Mutation Testing Framework wurden den Interfaces der ViewModels benannte

Konstante hinzugefügt, deren Werte den Namen der Eigenschaften entsprechen. Nach der Umbe-

nennung einer Eigenschaft muss dann nur noch der Wert der Konstanten an einer Stelle im Inter-

face des ViewModels angepasst werden.

Model

Alle sich am unteren Rand befindlichen Interfaces in Abbildung 11 stellen Abstraktionen von Mo-

dels dar und befinden sich zusammen mit ihren Standardimplementierungen in anderen Eclipse-

Plugins. Sie haben keinerlei Abhängigkeiten zur Benutzungsoberfläche und verwenden, falls nötig,

das Observer Entwurfsmuster, um Listener über Zustandsänderungen in Kenntnis zu setzen.

3.2.3.3 Darstellungs-Bug bei Verwendung einer SWT-Tabelle

In der für die Entwicklung verwendeten Version von Eclipse 3.6 (Helios Service Release 2) kommt es

unter bestimmten Bedingungen zu Darstellungsfehlern, wenn in Tabellen aus dem SWT Framework

Bilder mit transparentem Hintergrund eingebettet werden [Ecl11]. Der transparente Bereich des Bildes

erhält die Hintergrundfarbe der Tabelle. Das führt zu einem unschönen Effekt, wenn die Hintergrund-

farbe der Zeilen von der der Tabelle abweicht (s. Abbildung 12). Die Benutzungsoberfläche des Muta-

tion Testing Frameworks verwendet je nach Ergebnis der Auswertung des Mutanten eine andere Hin-

tergrundfarbe für eine Zeile. Andernfalls könnte die Hintergrundfarbe der Tabelle mit denen der einge-

färbten Zeilen synchronisiert werden. Um dieses Problem zu umgehen registriert sich die Mutation

Testing View als Listener an der Tabelle und färbt die einzelnen Zellen selbst ein (s. Listing 11).

Abbildung 12 Darstellungs-Bug – Vor und nach dem Workaround

Page 48: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

42 3 Implementierung

private void registerAsEraseItemListenerOnSolutionTable(

Table solutionsTable,

final ITableColorProvider tableColorProvider) {

solutionsTable.addListener(SWT.EraseItem, new Listener() {

@Override

public void handleEvent(Event event) {

Color background = tableColorProvider.getBackground(

event.item.getData(), event.index);

event.gc.setBackground(background);

event.gc.fillRectangle(event.getBounds());

}

});

}

Listing 11 Workaround zum Darstellungs – Bug SWT-Tabelle

3.3 Auszeichnung von Constraint-Regeln

Das Mutation Testing Framework macht keine Einschränkungen bezüglich der zu negierenden

Constraints. Dem Refacola-Entwickler ist es völlig freigestellt, welche Constraint-Regeln er auszeich-

nen möchte. Bei der Ausführung des Mutation Testing wird für jedes Constraint, das von einer ausge-

zeichneten Constraint-Regel erzeugt wird, eine Mutation generiert, die zu dem ursprünglichen

Constraint-System ein mutiertes Constraint-System aufbaut, in dem das negierte Constraint verwendet

wird. Eine Lösung des mutierten Constraint-Systems entspricht den an den Programmelementen

durchzuführenden Änderungen, um das ursprüngliche Programm in einen Mutanten zu überführen (s.

Abschnitt 3.2.1.4).

Die Laufzeit des Mutation Testing hängt wesentlich von der Anzahl der mutierten Constraint-Systeme

ab, zu denen der Constraint-Solver Lösungen soll. Deren Anzahl hängt direkt von der Summe der

Constraints, die von ausgezeichneten Constraint-System erzeugt werden, ab. Dadurch können gleich-

zeitig mehr Mutanten generiert werden. Die Menge der generierten ungültigen Mutanten, die keinen

Beitrag zur Erhöhung der Testabdeckung liefern, sollte deshalb minimiert werden. Von den erzeugten

Constraints können zwei Arten unterschieden werden.

Syntaktische und semantische Constraints

Die syntaktischen und semantischen Constraints stellen die Korrektheit des Programms im Sinne

der Sprachspezifikation der zugrundeliegenden Programmiersprache sicher und gewährleisten, dass

seine Kompilierung möglich ist. Unter bestimmten Bedingungen kann die Negierung von

Constraints dieser Art zu ausführbaren Programmen mit verändertem Verhalten führen [Bär10]. In

den meisten Fällen führt ihre Negierung aber zu ungültigen Mutanten. Die Auszeichnung von

Constraint-Regeln, die syntaktische und semantische Constraints erzeugen, stellt daher eher die

Ausnahme dar.

Bindungserhaltene Constraints

Die bindungserhaltenen Constraints sorgen dafür, dass das Programmverhalten erhalten bleibt. Die

Negierung von Constraints dieser Art kann zu Mutanten führen, die sich gegenüber dem ursprüng-

lichen Programm anders verhalten. Dies ist nicht garantiert und hängt von der konkreten Imple-

mentierung im Programm ab. Auf jeden Fall sind die generierten Mutanten gültig. Die Auszeich-

Page 49: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 43

nung von Constraint-Regeln, aus denen diese Art von Constraints erzeugt werden, sollte deshalb

angestrebt werden.

Refacola erlaubt die Zusammenlegung mehrerer Constraint-Regeln zu einer einzigen. Voraussetzung

ist, dass sie sich in den Bedingungen zur Erzeugung von Constraints gleichen. Semantisch hat dies

keine Auswirkungen auf die Refaktorisierungen von Refacola, da weiterhin dieselben Constraints

erzeugt werden. Auch das Mutation Testing Framework wird weder direkt noch indirekt von einer

Zusammenlegung mehrerer Constraint-Regeln beeinflusst. Die Datei, in der sie definiert sind, wird

dadurch kompakter und besser lesbar. Nun kann es vorkommen, dass eine Constraint-Regel

Constraints verschiedener Arten erzeugt. Wird sie ausgezeichnet, werden auch die von ihr erzeugten

syntaktischen und semantischen Constraints negiert, wodurch mehr ungültige Mutanten generiert wer-

den können. Diese Constraint-Regeln können, ohne dass die von ihr erzeugten Constraints und damit

die Refaktorisierungen von Refacola beeinflusst werden, wieder in mehrere Constraint-Regeln aufge-

teilt werden, von denen jede nur eine der zwei Arten von Constraints erzeugt. Dadurch hat der Refaco-

la-Entwickler die Möglichkeit, nur die Constraint-Regel auszuzeichnen, die bindungserhaltene

Constraints erzeugt.

Listing 12 stellt eine komplexe Constraint-Regel dar, die sich im Gegensatz zu den beiden elementa-

ren Constraint-Regeln in Listing 13 auf zwei Constraint-Regeln aufteilen lässt, weil sie mehr als ein

Constraint erzeugt. Die komplexe Constraint-Regel und die beiden elementaren Constraint-Regeln

sind semantisch äquivalent und lassen sich gegenseitig ineinander überführen. Mehrere von einer

Constraint-Regel erzeugte Constraints werden im Aktionsteil (then-Rumpf) durch Kommata getrennt.

Die elementaren Constraint-Regeln in Listing 13 lassen sich zu der in Listing 12 zusammenfassen, da

in beiden dieselben Bedingungen im Regelrumpf (if-Rumpf) für dieselben Programmelemente (for

all-Rumpf) vorkommen.

OOPSLA_f0_memberAccess

for all

rec: Java.DeclaredTypedEntityReference

ref: Java.MemberReference

M: Java.Member

do

if

Java.binds(ref, M),

Java.receiver(ref, rec)

then

rec.owner = ref.owner,

Java.sub*(rec.inferredDeclaredType, M.owner)

end

Listing 12 Komplexe Constraint-Regel

Page 50: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

44 3 Implementierung

OOPSLA_f0_memberAccess_1

for all

rec: Java.DeclaredTypedEntityReference

ref: Java.MemberReference

M: Java.Member

do

if

Java.binds(ref, M),

Java.receiver(ref, rec)

then

rec.owner = ref.owner

end

OOPSLA_f0_memberAccess_2

for all

rec: Java.DeclaredTypedEntityReference

ref: Java.MemberReference

M: Java.Member

do

if

Java.binds(ref, M),

Java.receiver(ref, rec)

then

Java.sub*(rec.inferredDeclaredType, M.owner)

end

Listing 13 Elementare Constraint-Regeln

Zur Auszeichnung von Constraint-Regeln in Refacola für das Mutation Testing wurde die in Listing

14 dargestellten Negatable-Annotation eingeführt. Sie dient als Marker für das Mutation Testing

Framework, um die zu negierenden Constraints zu bestimmen. Nachdem der Refacola-Datei, in der

Constraint-Regeln definiert werden, einmalig die Annotation durch Einfügen einer import-

Anweisung bekannt gemacht wurde, kann die Negatable-Annotation an Constraint-Regeln ange-

hängt werden (s. Listing 15). Wurden Änderungen an Constraint-Regeln durchgeführt, muss das zur

Generierung von Java Quellcode Dateien aus der Refacola DSL vorhandene Script ausgeführt werden,

damit die Änderungen für das Mutation Testing Framework sichtbar werden.

annotation Negatable()

Listing 14 Refacola-Typ zur Auszeichnung von Constraint-Regeln

Page 51: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 45

import "mutation.annotation.refacola"

@Negatable

OOPSLA_f0_nameBasedAccess

for all

r: Java.NamedReference

E: Java.NamedEntity

do

if

Java.binds(r, E)

then

r.identifier = E.identifier

end

Listing 15 Auszeichnung einer Constraint-Regel

Aus der Refacola DSL wird für jede definierte Constraint-Regel eine Java Klasse generiert. Für die

Negatable-Annotation wird eine Java Annotation mit demselben Namen erzeugt (s. Listing 16), auf

die während der Laufzeit per Reflection zugegriffen werden kann. Java Klassen, die aus Constraint-

Regeln generiert werden, verfügen, falls in Refacola zum Zeitpunkt der Generierung die Constraint-

Regeln ausgezeichnet waren, über die Java Annotation Negatable, welche an die Klasse selbst ange-

hängt wird (s. Listing 17).

@Retention(RetentionPolicy.RUNTIME)

public @interface Negatable { }

Listing 16 Generierte Java Annotation zu in Refacola definierten Annotation

@Negatable()

public class OOPSLA_f0_nameBasedAccess extends AbstractRule

Listing 17 Generierte Java Klasse aus ausgezeichneter Constraint-Regel

Während der Erzeugung des Constraint-Systems aus einem Programm beim Mutation Testing, wird

mittels Reflection geprüft, ob die Java Klasse der Constraint-Regel, aus der das jeweilige Constraint

erstellt wurde, über die Java Annotation Negatable verfügt. Alle von solch ausgezeichneten

Constraint-Regeln erzeugten Constraints werden während des Aufbaus des Constraint-Systems ge-

sammelt. Zusätzlich werden für die einzelnen gesammelten Constraints noch Referenzen auf die

Constraint-Regeln, von denen sie erzeugt wurden, verwaltet. Aus den gesammelten Constraints wer-

den dann negierte Constraints abgeleitet. Ein ursprüngliches Constraint, seine negierte Form, die zu-

gehörige Constraint-Regel sowie das komplette Constraint-System ergeben zusammen eine Mutation

(s. Abschnitt 3.2.1.4). Jede Mutation kann aus diesen Informationen ein mutiertes Constraint-System

erstellen, dessen Lösungen zur Erzeugung von Mutanten aus dem ursprünglichen Programm verwen-

det werden. Für die Erstellung eines mutierten Constraint-Systems sind die in den Mutationen verwal-

teten Constraint-Regeln nicht weiter relevant. Sie werden aber zur Darstellung in der Benutzungsober-

fläche verwendet, um dem Entwickler Informationen über die Herkunft der Constraints zu geben.

Page 52: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

46 3 Implementierung

3.4 Beispiel zur Anwendung des Mutation Testing Frameworks

In diesem Abschnitt wird ausgehend von den in Refacola definierten Constraint-Regeln die Anwen-

dung des Mutation Testing Frameworks an einem kleinen Java-Programm demonstriert. Zuerst wird

eine Constraint-Regel formuliert, dessen Constraints die syntaktische und semantische Korrektheit des

Java-Programms im Kontext überschriebener Methoden sicherstellen (s. Listing 18). Methodenpara-

meter, Rückgabetypen sowie die Deklarationen von Checked Exceptions werden ausgeblendet und

nicht weiter betrachtet. Die Constraint-Regel aus Listing 18 stellt jede Methode einer Subklasse jeder

Methode einer Superklasse gegenüber und prüft, ob die Methode der Subklasse die der Superklasse

überschreibt. Dies ist nicht der Fall, wenn beide Methoden unterschiedliche Bezeichner besitzen oder

die Methode in der Superklasse über den Sichtbarkeitsmodifikator private verfügt. In diesen Situati-

onen ist das erzeugte Constraint stets erfüllt. In allen anderen Fällen überschreibt die Methode in der

Subklasse die der Superklasse. Gemäß der Java Sprachspezifikation muss die überschreibende Metho-

de der Subklasse zumindest die gleiche Sichtbarkeit besitzen wie die überschriebene Methode der

Superklasse. Dies wird durch die Teilbedingung

SubMethod.accessibility >= SuperMethod.accessibility

des Constraints ausgedrückt. Eine Verletzung führt zu einem nicht kompilierbaren Programm. Die

Constraint-Regel erzeugt somit ausschließlich syntaktische und semantische Constraints und wird für

das Mutation Testing nicht ausgezeichnet.

MGR_methodOverriding

for all

SuperClass: Java.Class

SubClass: Java.Class

SuperMethod: Java.InstanceMethod

SubMethod: Java.InstanceMethod

do

if

Java.sub(SubClass, SuperClass),

Java.member(SuperClass, SuperMethod),

Java.member(SuperClass, SubMethod)

then

SuperMethod.identifier != SubMethod.identifier or

SuperMethod.accessibility = #private or

SubMethod.accessibility >= SuperMethod.accessibility

end

Listing 18 Constraint-Regel zur Sicherstellung syntaktischer und semantischer Korrektheit bei überschriebenen

Methoden

Die nachfolgende Constraint-Regel (Listing 19) wird nun genutzt, um die dynamische Bindung des

Methodenaufrufs im Java-Programm, das als Beispiel verwendet wird, zu entfernen. Falls in der Su-

perklasse ein Aufruf einer Methode vorhanden ist und die Methode denselben Bezeichner besitzt wie

eine Methode aus einer der Subklassen, dann muss die Methode der Superklasse über eine höhere

Sichtbarkeit verfügen als private. Damit ist sichergestellt, dass die Methode der Subklasse die der

Superklasse überschreibt. Die Auszeichnung dieser Constraint-Regel mit der Negatable-Annotation

führt dann dazu, dass das erzeugte Constraint negiert wird und die Sichtbarkeit private für die in der

Page 53: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 47

Superklasse vorhandene Methode gesetzt werden muss, um eine Lösung des Constraint-Systems zu

erhalten.

@Negatable

MGR_methodOverridingAccess

for all

reference: Java.MemberReference

SuperClass: Java.Class

SubClass: Java.Class

SuperMethod: Java.InstanceMethod

SubMethod: Java.InstanceMethod

do

if

Java.binds(reference, SuperMethod),

Java.sub(SubClass, SuperClass),

Java.member(SuperClass, SuperMethod),

Java.member(SubClass, SubMethod)

then

SuperMethod.identifier = SubMethod.identifier ->

SuperMethod.accessibility > #private

end

Listing 19 Constraint-Regel zum Erhalt der dynamischen Bindung bei überschriebenen Methoden

In Listing 20 ist links das Java-Programm dargestellt, aus dem Mutanten generiert werden. Die Me-

thode getClassName() der Klasse A wurde in Klasse B überschrieben. Abhängig vom Typ des Ob-

jekts wird in der Methode getName() zur Laufzeit entweder A.getClassName() oder

B.getClassName() aufgerufen. Die Negierung des von der Constraint-Regel aus Listing 19 erzeug-

ten Constraints verlangt, dass die Sichtbarkeit der Methode A.getClassName() auf private redu-

ziert wird. Als Resultat entsteht der Mutant, der in Listing 20 rechts dargestellt ist. Die Methode

getClassName() wird von Klasse B nicht mehr überschrieben und getName() ruft jetzt unabhängig

vom Typ des Objekts stets die Methode A.getClassName() auf. Während im ursprünglichen Pro-

gramm ein Aufruf von getName() auf einem Objekt vom Typ B den Wert "B" liefert, gibt der Mutant

beim Aufruf derselben Methode auf einem Objekt desselben Typs den Wert "A" zurück. In Listing 21

ist der verwendete JUnit-Test dargestellt, der den Mutanten erkennt. Die Ergebnisse des Mutation

Testing Durchlaufs werden in Abbildung 13 aufgelistet.

Page 54: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

48 3 Implementierung

class A {

public String getClassName() {

return "A";

}

public String getName() {

return getClassName();

}

}

class B extends A {

public String getClassName() {

return "B";

}

}

class A {

private String getClassName() {

return "A";

}

public String getName() {

return getClassName();

}

}

class B extends A {

public String getClassName() {

return "B";

}

}

Listing 20 Ursprüngliches Programm (links) und generierter Mutant mit minimalen Änderungen (rechts)

public class BTests {

@Test

public void toString_newInstance_returnsB() {

B cut = new B();

String result = cut.getName();

assertTrue(result.equals("B"));

}

}

Listing 21 JUnit-Test zum Anwendungsbeispiel

Abbildung 13 Ergebnisse des Anwendungsbeispiels

Page 55: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 49

Der Mutant in Listing 20 weicht syntaktisch kaum vom ursprünglichen Programm ab. Das Mutation

Testing Framework hat aus der Menge der vom Constraint-Solver berechneten Lösungen zu dem mu-

tierten Constraint-System diejenige ausgewählt, die am wenigsten Programmelemente verändert. Die

Anzahl zu berechnender Lösungen wurde nicht beschränkt. Selbst für dieses kleine Java-Programm

konnte der verwendete Constraint-Solver Choco 192 Lösungen finden.

Da verschiedene Lösungen desselben Constraint-Systems zu paarweise verhaltensäquivalenten Pro-

grammen führen, vorausgesetzt die Constraint-Regeln sind vollständig und korrekt, würde die Berech-

nung von höchstens einer Lösung für jedes mutierte Constraint-System ausreichen und zudem die

Laufzeit des Mutation Testing reduzieren. Diese Lösungen müssen nicht minimal in Bezug auf die

syntaktischen Änderungen am Programm sein. Listing 22 rechts zeigt den Mutanten, der entsteht,

wenn maximal eine Lösung vom Constraint-Solver berechnet wird. Das ursprüngliche Ziel, dass die

Methode B.newgetClassName() die Methode A.newgetClassName() nicht mehr überschreibt,

wird ebenfalls erreicht.

class A {

public String getClassName()

{

return "A";

}

public String getName() {

return getClassName();

}

}

class B extends A {

public String getClassName()

{

return "B";

}

}

class newA {

private String newgetClassName()

{

return "A";

}

private String newgetName() {

return newgetClassName();

}

}

class B extends newA {

public String newgetClassName()

{

return "B";

}

}

Listing 22 Ursprüngliches Programm (links) und generierter Mutant (rechts),

JUnit-Test liegt in einem anderem Java-Projekt

Allerdings ergeben sich durch die zusätzlichen Änderungen an den Programmelementen neue Proble-

me. Wie in Abbildung 13 zu sehen ist, wurde als Testbasis ein anderes Java-Projekt verwendet, aus

dem die Methode B.getName() referenziert wird. Die Klasse B des Mutanten verfügt aber über keine

Methode mit diesem Namen. Die Testbasis kann daher für diesen Mutanten nicht ausgeführt werden,

da sie in diesem Fall nicht kompilierbar ist. Dieses Problem wird in Abschnitt 3.5 genauer betrachtet.

Page 56: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

50 3 Implementierung

Wird der JUnit-Test aus Listing 21 in das Java-Projekt MutationExample verschoben, generiert das

Mutation Testing Framework den Mutanten in Listing 23 rechts, wenn maximal eine Lösung berech-

net wird. Da sich die Klasse, welche den JUnit-Test beinhaltet, nun im selben Java-Projekt befindet,

werden auch für ihre Programmelemente Constraints erzeugt und bei der Suche nach einer Lösung

berücksichtigt. Dadurch ist sichergestellt, dass die Methode newgetName() weiterhin von der Klasse

newBTests aufgerufen werden kann. Das Problem besteht darin, dass die Sichtbarkeit der Klasse

newBTests reduziert wird (s. Listing 24). Obwohl dies kein syntaktischer oder semantischer Fehler

ist, kann der enthaltene JUnit-Test nicht mehr vom JUnit 4 Framework ausgeführt werden. JUnit 4

verlangt für Klassen, die JUnit-Tests beinhalten, dass diese mit der Sichtbarkeit public gekennzeich-

net sind. In der Folge meldet JUnit 4 fehlgeschlagene Tests, woraus das Mutation Testing Framework

die Erkennung des Mutanten ableitet. Auch dieses Problem wird in Abschnitt 3.5 aufgegriffen.

class A {

public String getClassName()

{

return "A";

}

public String getName() {

return getClassName();

}

}

class B extends A {

public String getClassName()

{

return "B";

}

}

class newA {

private String newgetClassName()

{

return "A";

}

String newgetName() {

return newgetClassName();

}

}

class newB extends newA {

public String newgetClassName()

{

return "B";

}

}

Listing 23 Ursprüngliches Programm (links) und generierter Mutant (rechts)

JUnit-Test liegt im selben Java-Projekt

class newBTests {

@Test

public void toString_newInstance_returnsB() {

newB cut = new newB();

String result = cut.newgetName();

assertTrue(result.equals("B"));

}

}

Listing 24 JUnit-Test zum Mutanten in Listing 23 rechts

Page 57: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 51

3.5 Mögliche Verbesserungen und Erweiterungen

Die Implementierung des Mutation Testing Frameworks bietet dem Refacola-Entwickler durch die

Auszeichnung von Constraint-Regeln eine einfache und flexible Möglichkeit an, ohne Änderungen

oder Erweiterungen des Quellcodes Einfluss auf die Generierung von Mutanten zu nehmen. Die Muta-

tion Testing View unterstützt den Entwickler, der Mutation Testing für seine Programme benutzen

möchte, bei der Untersuchung von Mutanten. Während der Entwicklung des Mutation Testing Frame-

works entwickelten sich einige Ideen zur Verbesserung der Implementierung. Im Rahmen dieser Ar-

beit war es zeitlich nicht möglich diese umzusetzen.

Auf Kopien von Programmen arbeiten

Während eines Mutation Testing Durchlaufs werden Mutanten aus einem vorher ausgewählten

Programm generiert. Dabei wird das Programm selbst geändert und anschließend neu kompiliert.

Die durchgeführten Änderungen werden danach wieder rückgängig gemacht. Dies stellt kein Prob-

lem dar, solange der Mutation Testing Durchlauf ordnungsgemäß beendet oder durch den Entwick-

ler abgebrochen wird. In beiden Fällen wird der Quellcode des Programms wieder in seinen vorhe-

rigen Zustand überführt. Wird aber die Ausführung der Entwicklungsumgebung Eclipse abrupt ab-

gebrochen, beispielsweise durch einen Systemfehler, bei dem das Mutation Testing Framework

nicht mehr die Möglichkeit hat die Änderungen zurückzusetzen, verbleibt der Quellcode des Pro-

gramms in seinem geänderten Zustand. Der Entwickler ist dann dafür verantwortlich das Pro-

gramm wiederherzustellen, indem er z. B. die von den Änderungen betroffenen Stellen im Quell-

code manuell sucht und umschreibt. Die Wahrscheinlichkeit eines abrupten Abbruchs wird umso

größer, je mehr Zeit zwischen den Änderungen am Programm und dem Zurücksetzen der Änderun-

gen liegt. Mit dem Anzeigen eines Mutanten werden Änderungen am Programm durchgeführt, die

erst mit dem Anzeigen des ursprünglichen Quellcodes oder während der Freigabe von Ressourcen

des Mutation Testing Frameworks beim Beenden von Eclipse wieder zurückgesetzt werden. Um

diese Probleme zu lösen, kann von dem Programm eine temporäre Kopie erzeugt werden, die für

das Mutation Testing verwendet wird. Die Kopie könnte zu Beginn eines Mutation Testing Durch-

laufs automatisch erstellt werden.

Manipulation von Programmelementen für mehrere Java-Projekte

Damit Lösungen für ein mutiertes Constraint-System, dieses ist durch das ursprüngliche

Constraint-System entstanden, in dem ein ausgewähltes Constraint negiert wurde, gefunden werden

können, müssen Werte von Constraint-Variablen verändert werden. Diese Veränderungen wirken

sich entsprechend auf den Quellcode des Mutanten aus und können beispielsweise die Sichtbarkeit

einer Methode reduzieren oder den Bezeichner einer Klasse manipulieren. Die syntaktischen und

semantischen Constraints gewährleisten, dass der aus einer Lösung erstellte Mutant kompilierbar

ist.

Nun kann es durchaus sein, dass aus einem anderen Java-Projekt heraus auf eine Klasse verwiesen

wird, die Teil desjenigen Projekts ist, aus dem Mutanten generiert werden Das erstere Java-Projekt

kann die JUnit-Tests beinhalten, die als Testbasis für das Mutation Testing verwendet werden. In

einem solchen Fall wäre die Testbasis nicht mehr kompilierbar, da sie die Klasse, dessen Bezeich-

ner manipuliert wurde, nicht mehr referenzieren kann. Um diese Referenzen anzupassen, müssen

nicht nur für das Java-Projekt, aus dem Mutanten generiert werden, sondern auch für die Testbasis,

falls es sich nicht um dasselbe Java-Projekt handelt, Constraints erzeugt und zu einem Constraint-

Page 58: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

52 3 Implementierung

System zusammengefasst werden. Daraus kann dann der Constraint-Solver Lösungen berechnen,

die sicherstellen, dass auch die Testbasis kompilierbar bleibt.

Refacola bietet mit dem MultiProgramInfoProvider die Möglichkeit Faktenbasen und Pro-

grammelemente mehrerer Java-Projekte zu erzeugen. Mit ihrer Hilfe kann ein Constraint-System

aufgebaut werden, das aus den Constraints verschiedener Java-Projekte besteht. Das weitere Vor-

gehen zur Generierung von Mutanten erfolgt analog zu Abschnitt 3.2.1. Der MultiProgramInfo-

Provider könnte unter Verwendung der in Refacola enthaltenen Klasse Manipulator aus den

Informationen eines IChangeSets die von Eclipse verwendeten Changes zur Durchführung von

Änderungen am Quellcode berechnen. Allerdings unterstützt der Manipulator zur Zeit keine Be-

rechnung von Änderungen verschiedener Java-Projekte, weshalb der MultiProgramInfoProvi-

der nicht vom Mutation Testing Framework verwendet wird.

Constraints für JUnit-Test-Klassen

An Klassen, in denen JUnit-Tests enthalten sind (im folgenden JUnit-Test-Klassen genannt), stel-

len die JUnit Frameworks besondere Anforderungen.

JUnit 3 verlangt, dass alle JUnit-Test-Klassen stets über die Sichtbarkeit public verfügen und von

der Klasse TestCase erben. Zur Identifikation der durch das JUnit 3 Framework auszuführenden

Methoden müssen diese im Bezeichner das Präfix test verwenden.

Auch JUnit 4 verlangt, dass die JUnit-Test-Klassen die Sichtbarkeit public haben. Methoden, die

vom JUnit 4 Framework ausgeführt werden sollen, werden nicht mehr über ihren Bezeichner son-

dern über eine Annotation Test identifiziert, die an entsprechende Methoden angeheftet wird.

Für das Mutation Testing Framework sind diese besonderen Anforderungen, die an bestimmte

Klassen und Methoden zu stellen sind, nicht ersichtlich. Es kann also durchaus sein, dass eine vom

Constraint-Solver berechnete Lösung ausgewählt wird, die zu einer Änderung der Sichtbarkeit ei-

ner JUnit-Test-Klasse führt. Das JUnit Framework würde in einem solchen Fall das Scheitern eines

oder mehrerer Tests melden und der Mutant würde dann fälschlicherweise als erkannt angesehen.

Dieses Problem kann über Constraints gelöst werden, die sicherstellen, dass die besonderen Anfor-

derungen eingehalten werden. Listing 25 zeigt wie eine Constraint-Regel aussehen könnte, die si-

cherstellt, dass JUnit-Test-Klassen für das JUnit 3 Framework die Sichtbarkeit public beibehal-

ten. Refacola müsste entsprechend erweitert werden, um auch Abfragen bezüglich Annotationen

durchführen zu können.

MGR_junit3_testclass_public

for all

TestCaseClass: Java.TopLevelClass

TestClass: Java.Class

do

if

Java.sub(TestClass, TestCaseClass)

then

TestCaseClass.identifier = 'TestCase' ->

TestClass.accessibility = #public

end

Listing 25 Constraint-Regel für Sichtbarkeit von JUnit-3-Test-Klassen

Page 59: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

3 Implementierung 53

Parallele Verarbeitung

Das Mutation Testing Framework arbeitet strikt sequenziell. Nach Prüfung der Vorbedingungen für

das Programm und die Testbasis erfolgt der Aufbau der Java-Faktenbasis. Mit dessen Hilfe sowie

der in Refacola vorhandenen Constraint-Regeln wird das Constraint-System des Programms aufge-

baut. Sowohl die Erstellung der Java-Faktenbasis als auch die Erzeugung des Constraint-Systems

können bei größeren Programmen durchaus einige Zeit in Anspruch nehmen. Es wäre zu prüfen,

welche Operationen parallelisierbar sind, um die Leistung heutiger und zukünftiger Mehrkernpro-

zessoren auszunutzen, um die Laufzeit zu reduzieren. Davon würden auch die Refaktorisierungen

in Refacola profitieren, da sie ebenfalls auf die Erstellung von Java Faktenbasen angewiesen sind.

Die Abarbeitung der Mutationen erfolgt in einer Schleife. Für jede Mutation wird eine Reihe von

Schritten ausgeführt, von der Suche nach einer Lösung für das mutierte Constraint-System bis zur

Ausführung der Testbasis unter Verwendung des generierten Mutanten (s. Algorithmus 2). Wenn

zwischen den Mutationen keine Datenabhängigkeit besteht, könnte die Schleife parallelisiert wer-

den. In der aktuellen Implementierung des Mutation Testing Frameworks teilen sich die Mutatio-

nen das Programm, welches zum Mutation Testing ausgewählt wurde. Jede Mutation manipuliert

das Programm, um aus diesem einen Mutanten zu erzeugen, und setzt die Änderungen anschlie-

ßend zurück, bevor die nächste Mutation das Programm verändert. Hierbei handelt es sich im Sinne

der Parallelverarbeitung um einen kritischen Abschnitt, in dem sich zu jedem Zeitpunkt maximal

ein Prozess oder ein Thread befinden darf. Wird die Datenabhängigkeit zwischen den Mutationen

aufgelöst, entfällt der kritische Abschnitt und die Schleife kann parallelisiert werden. Die Anferti-

gung von Kopien des Programms würde dieses Problem lösen. Jeder Thread, der zur Parallelverar-

beitung eingesetzt wird, könnte eine Menge von Mutationen erhalten, die er sequenziell abarbeitet.

Dabei wird für jeden dieser Threads eine Kopie des Programms erzeugt. Die Mutationen, die

Thread n bearbeitet, würden dann Kopie n verwenden. Zu klären wäre, ob die Java-Faktenbasis

sowie das Constraint-System nur einmal erstellt werden können und dann für alle Kopien des Pro-

gramms gelten. Weiterhin ist zu klären, inwieweit der durch die Kopien zusätzlich benötigte Spei-

cherbedarf eine Einschränkung darstellt.

Unterstützung weiterer Programmiersprachen

Ziel des Mutation Testing Frameworks ist die Generierung von Mutanten aus Java Programmen. Es

stellt eine Erweiterung von Refacola dar und setzt auf ihrem Konzept der Constraint-basierten Re-

faktorisierung auf. Refacola selbst ist aber nicht auf eine Programmiersprache festgelegt. Sie kann

nicht nur dazu verwendet werden, um Refaktorisierungen innerhalb einer konkreten Programmier-

sprache, sondern auch um Refaktorisierungen zwischen verschiedenen Programmiersprachen zu

definieren und durchzuführen. Während Refacola durch das Hinzufügen weiterer Sprachdefinitio-

nen, Constraint-Regeln und Faktenbasen weitere Programmiersprachen unterstützen kann, bleibt

das Mutation Testing Framework in seiner aktuellen Implementierung auf Java Programme be-

schränkt. Obwohl wo immer möglich Komponenten von Refacola verwendet werden, um weitest-

gehend von einer konkreten Programmiersprache zu abstrahieren, waren Abhängigkeiten zur Java

Programmiersprache nicht gänzlich zu vermeiden. Dies trifft insbesondere auf die Nutzung des JU-

nit Frameworks zu.

Page 60: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

54 3 Implementierung

Page 61: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

4 Zusammenfassung und Fazit 55

4 Zusammenfassung und Fazit

Automatisierte Tests können während der Entwicklung und der Wartung von Software eingesetzt wer-

den, um Fehler frühzeitig zu entdecken. Bei Änderungen an der Struktur der Software im Zuge von

teils mit Werkzeugen durchgeführten, teils manuell durchgeführten Refaktorisierungen und dem Hin-

zufügen weiterer Funktionen können sie unbeabsichtigte Änderungen am Programmverhalten entde-

cken und sicherstellen, dass vorhandene Funktionalität nicht zerstört wird. Dazu ist es erforderlich,

dass die Testabdeckung ausreichend ist, um Fehlverhalten seitens das Programms entdecken zu kön-

nen.

Beim Mutation Testing werden durch automatisierte Manipulationen an der Code-Basis eines Pro-

gramms Mutanten generiert. Sie sollen kompilierbar sein und ein dem ursprünglichen Programm ver-

ändertes, aber nicht erwünschtes Verhalten zeigen. Eine Testbasis soll dann diese Mutanten erkennen.

Nicht erkannte Mutanten lassen auf eine nicht ausreichende Testabdeckung schließen. Der Entwickler

kann durch Untersuchung der Mutanten Testdaten gewinnen, aus denen weitere Testfälle zur Erhö-

hung der Testabdeckung entwickelt werden können.

Es wurde aufgezeigt, dass die Schwierigkeit beim Mutation Testing in der Generierung von Mutanten

hoher Qualität liegt. Können äquivalente Mutanten vermieden werden, erhöht sich die Effizienz des

Mutation Testing, da der Entwickler diese Mutanten nicht zeitaufwändig untersuchen und manuell

herausfiltern muss. Der Verzicht auf die Generierung nicht kompilierbarer Mutanten reduziert die

Laufzeitkosten und damit die Dauer eines Mutation Testing Durchlaufs. Es wurde diskutiert, wie die

constraint-basierte Refaktorisierung als Grundlage verwendet und angepasst werden kann, um die

Generierung nicht kompilierbarer Mutanten zu vermeiden und die Anzahl generierter äquivalenter

Mutanten zu reduzieren.

In Anschluss daran wurden einige Ausschnitte aus der sich noch in der Entwicklung befindlichen Re-

facola vorgestellt. Refacola verwendet die Technik der constraint-basierten Refaktorisierung, um si-

cherzustellen, dass das resultierende Programm nach einer Refaktorisierung weiterhin kompilierbar ist

und sein Verhalten beibehält. Durch die Vorzüge, die eine Anpassung der bei der constraint-basierten

Refaktorisierung verwendeten Technik für das Mutation Testing bietet, wurde Refacola im Rahmen

dieser Arbeit um ein Mutation Testing Framework erweitert.

Ausgehend von der Sicht des Entwicklers wurden Anforderungen an das Mutation Testing Framework

ausgearbeitet und diskutiert. Während sich andere Ansätze zur constraint-basierten Generierung von

Mutanten, in [Bär10] werden Type Constraints und in [S+T10] Accessibility Constraints betrachtet,

auf bestimmte Constraints beschränken, macht das Mutation Testing Framework diesbezüglich keine

Einschränkungen. Der Refacola-Entwickler kann durch Auszeichnung von in Refacola definierten

Constraint-Regeln die Constraints bestimmen, die im Zuge des Mutation Testing zur Generierung von

Mutanten negiert werden sollen. Das Mutation Testing Framework stellt darüber hinaus eine Eclipse-

View zur Verfügung, die alle generierten Mutanten auflistet und dem Entwickler die Möglichkeit gibt

diese zu untersuchen.

Kern des Mutation Testing Frameworks ist der verwendete Algorithmus zur Generierung und Auswer-

tung von Mutanten. Während einige Funktionen von Refacola unverändert übernommen werden konn-

ten, wie die Generierung der Java-Faktenbasis, mussten andere im Mutation Testing Framework er-

neut implementiert werden, was zu einer Dopplung von Code geführt hat. Dazu zählt der

CompleteConstraintSetGenerator, der für die Erzeugung von Constraints aus Programmen

verantwortlich ist. Nur während der Generierung von Constraints konnte eine Verbindung zwischen

Page 62: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

56 4 Zusammenfassung und Fazit

Constraint-Regeln und Constraints hergestellt werden. Ein Eingriff in den vom

CompleteConstraintSetGenerator implementierten Algorithmus ist leider nicht möglich. Dies

ist für die Umsetzung aber notwendig gewesen, da nur über die Auszeichnung der Constraint-Regeln

die für das Mutation Testing zu negierenden Constraints identifiziert werden können.

Die Auszeichnung der Constraint-Regeln zur Bestimmung der zu negierenden Constraints wurde mög-

lichst einfach gehalten. Es genügt entsprechende auszuwählen und mit einer Annotation zu versehen.

Dies erfolgt ebenso deklarativ wie die Spezifizierung von Constraint-Regeln und fügt sich damit naht-

los in Refacola ein.

Die Qualität der generierten Mutanten hängt nicht zuletzt von den spezifizierten Constraint-Regeln ab.

Refacola befindet sich zum Zeitpunkt der Erstellung dieser Arbeit noch in der Entwicklung, weshalb

die vorhandenen Constraint-Regeln noch unvollständig sind und nicht die gesamte Sprachspezifikation

von Java abdecken. Dadurch werden häufig Mutanten generiert, die nicht kompilierbar sind. Die Un-

vollständigkeit der Constraint-Regeln hat ebenfalls Auswirkungen auf die Lösungen des Constraint-

Systems. Es kann passieren, dass unterschiedliche Lösungen desselben Constraint-Systems zu unter-

schiedlichen Arten von Mutanten führen. Während eine Lösung zu einem relevanten Mutanten führt,

kann eine andere Lösung wiederum in einen ungültigen Mutanten resultieren.

Das Mutation Testing Framework kann darüber hinaus noch ausgebaut und verbessert werden. So

wird bei der Generierung von Mutanten stets das ursprüngliche Programm manipuliert mit dem ent-

sprechenden Risiko, dass die durchgeführten Änderungen bei einem Systemfehler nicht mehr zurück-

gesetzt werden können. In diesem Fall muss der Entwickler die Änderungen am Programm manuell

rückgängig machen. Hier wäre es ratsam während des Mutation Testing auf einer Kopie des Pro-

gramms zu arbeiten. Das hätte ebenfalls den Vorteil, dass große Teile des Algorithmus parallelisiert

werden können, da jeder Thread dann eine eigene Kopie des Programms verwenden kann. Da sowohl

die in Refacola spezifizierten Refaktorisierungen als auch das Mutation Testing Framework dieselben

Constraint-Regeln verwenden, profitieren beide gleichermaßen von ihrer Vervollständigung, die im

Laufe der Entwicklung von Refacola noch erfolgen wird.

Page 63: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

5 Literaturverzeichnis 57

5 Literaturverzeichnis

[Bär10] Mutantengenerierung durch Type Constraints

BÄR, ROBERT

Bachelorarbeit

Fernuniversität in Hagen

August 2010

http://www.fernuni-hagen.de/imperia/md/content/ps/bachelorarbeit-baer.pdf

Zugriff: 18. Februar 2012

[Ecl11] Bug 50163 – Table doesn't respect transparency in column images when using a different

row background color

https://bugs.eclipse.org/bugs/show_bug.cgi?id=50163

Zugriff: 27. Dezember 2011

[Ezr09] MVVM for .NET Winforms – MVP-VM (Model View Presenter – View Model)

Introduction

EZRA, AVIAD

August 2009

http://aviadezra.blogspot.com/2009/08/mvp-mvvm-winforms-data-binding.html

Zugriff: 17. Dezember 2011

[J+H] An Analysis and Survey of the Development of Mutation Testing

(JIA, YUE), (HARMAN, MARK)

Journal

IEEE Transactions on Software Engineering

Volume 37 Issue 5, September 2011

IEEE Press Piscataway, NJ, USA

September 2011

[Kre11] Systematisches Testen von Constraintregeln

KREIS, MARIUS

Masterarbeit

Fernuniversität in Hagen

Mai 2011

http://www.fernuni-hagen.de/imperia/md/content/ps/masterarbeit-kreis.pdf

Zugriff: 18. Februar 2012

[Off92] Investigations of the Software Testing Coupling Effect

OFFUTT, A. J.

Journal

ACM Transactions on Software Engineering and Methodology (TOSEM)

Volume 1 Issue 1, Jan. 1992

ACM New York, NY, USA

Januar 1992

[Osh10] The Art of Unit Testing Deutsche Ausgabe

OSHEROVE, ROY

November 2010

mitp

ISBN 978-3-8266-9023-5

Page 64: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

58 5 Literaturverzeichnis

[S+T09] From Public to Private to Absent: Refactoring JAVA Programs under Constrained

Accessibility

(STEIMANN, FRIEDRICH), (THIES, ANDREAS)

Proceeding

Genoa Proceedings of the 23rd European Conference on ECOOP 2009 –

Object Oriented Programming

Springer-Verlag Berlin, Heidelberg ©2009

Juli 2009

http://www.fernuni-hagen.de/ps/pubs/ECOOP2009.pdf

Zugriff: 18. Februar 2012

[S+T10] From Behaviour Preservation to Behaviour Modification:

Constraint-Based Mutant Generation

(STEIMANN, FRIEDRICH), (THIES, ANDREAS)

Proceeding

ICSE '10 Proceedings of the 32nd ACM/IEEE International

Conference on Software Engineering – Volume 1

ACM New York, NY, USA ©2010

Mai 2010

[SKP11] A Refactoring Constraint Language and its Application to Eiffel

(STEIMANN, FRIEDRICH), (KOLLEE, CHRISTIAN), (VON PILGRIM, JENS)

Proceeding

ECOOP'11 Proceedings of the 25th European conference on

Object-oriented programming

Springer-Verlag Berlin, Heidelberg ©2011

Juli 2011

http://www.feu.de/ps/pubs/ECOOP2011.pdf

Zugriff: 18. Februar 2012

[Ste10] Korrekte Refaktorisierungen: Der Bau von Refaktorisierungswerkzeugen als

eigenständige Disziplin

STEIMANN, FRIEDRICH

OBJEKTspektrum 4

Seite 24 - 29

April 2010

http://www.sigs-datacom.de/fileadmin/user_upload/zeitschriften/os/2010/04/

steimann_OS_04_10.pdf

Zugriff: 25. Februar 2012

[Ste11] Constraint-Based Model Refactoring

STEIMANN, FRIEDRICH

Proceeding

MODELS'11 Proceedings of the 14th international conference on

Model driven engineering languages and systems

Springer-Verlag Berlin, Heidelberg ©2011

Oktober 2011

http://www.feu.de/ps/pubs/MoDELS2011.pdf

Zugriff: 25. Februar 2012

Page 65: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

5 Literaturverzeichnis 59

[Stu11] Automatisierte Analyse von C#-Programmen für das Pull-Up-Field-Refactoring in

MonoDevelop

STUMPF, KEVIN

Bachelorarbeit

Fernuniversität in Hagen

September 2011

http://www.fernuni-hagen.de/imperia/md/content/ps/bachelorarbeit-stumpf.pdf

Zugriff: 18. Februar 2012

[TKB03] Refactoring for Generalization using Type Constraints

(TIP, FRANK), (KIEZUN, ADAM), (BÄUMER, DIRK)

OOPSLA '03 Proceedings of the 18th annual ACM SIGPLAN conference on

Object oriented programing, systems, languages, and applications

ACM New York, NY, USA ©2003

November 2003

[Wei11] Grundlagen der Theoretischen Informatik A

WEIHRAUCH, K.

Studienbrief zum Kurs 1657

Fernuniversität in Hagen

2011

Page 66: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

60 5 Literaturverzeichnis

Page 67: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

A Inhalt der beiliegenden DVD 61

A Inhalt der beiliegenden DVD

.\Bachelorarbeit

Enthält diese Bachelorarbeit im PDF-Format.

.\MutationTesting\annotation

Enthält die Datei mutation.annotation.refacola, welche die zur Auszeichnung von

Constraint-Regeln benötigte Negatable-Annotation zur Verfügung stellt.

.\MutationTesting\doc

Enthält die Quellcode-Dokumentation des Mutation Testing Frameworks.

.\MutationTesting\example

Enthält das Java-Programm, das als Beispiel in Abschnitt 3.4 verwendet wird, sowie die JUnit

Tests, welche die aus dem Java-Programm generierten Mutanten erkennen.

.\MutationTesting\src

Enthält den Quellcode des Mutation Testing Frameworks, das im Rahmen dieser Arbeit entwickelt

wurde.

.\Refacola\src

Enthält den Quellcode von Refacola (Stand: 19. Februar 2012).

.\Refacola_MutationTesting\release

Enthält das Mutation Testing Framework sowie Refacola (Stand: 19. Februar 2012) in Form von

Plugins zur Verwendung in Eclipse.

Die in Refacola definierten Constraint-Regeln für Java wurden um die in Abschnitt 3.4 verwende-

ten Constraint-Regeln ergänzt. Die Constraint-Regel MWA_noNameCollisionTopLevelTypes

wurde auskommentiert.

.\Refacola_MutationTesting\src

Enthält den Quellcode des Mutation Testing Frameworks und den Quellcode von Refacola (Stand:

19. Februar 2012).

Dem Java-Projekt de.feu.ps.refacola.lang.java wurde im Paket refacola die Datei mu-

tation.annotation.refacola beigelegt, welche die für das Mutation Testing nötige

Negatable-Annotation umfasst. Die Datei Ruleset.refacola im selben Paket wurde um die

Import-Anweisung zur Einbindung der Negatable-Annotation ergänzt und die Constraint-Regel

MWA_noNameCollisionTopLevelTypes wurde auskommentiert. Desweiteren wurden die in

Abschnitt verwendeten Constraint-Regeln hinzugefügt.

Page 68: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

62 A Inhalt der beiliegenden DVD

.\Eclipse

Enthält Eclipse 3.6 (Helios Service Release 2, 32-bit) für Windows mit allen Plugins, die für die

Entwicklung des Mutation Testing Frameworks und von Refacola (Stand: 19. Februar 2012) benö-

tigt werden.

Der von Eclipse maximal zu nutzende Arbeitsspeicher wurde auf 512 MB erhöht.

Darüber hinaus sind sowohl das Mutation Testing Framework als auch Refacola in dieser Version

von Eclipse integriert. Die in Refacola definierten Constraint-Regeln für Java wurden um die in

Abschnitt 3.4 verwendeten Constraint-Regeln ergänzt. Die Constraint-Regel

MWA_noNameCollisionTopLevelTypes wurde auskommentiert.

.\Eclipse\jdk\doc

Enthält die API-Dokumentation vom JDK 6.

.\Eclipse\jdk\src

Enthält den Quellcode vom JDK 6.

.\JDK

Enthält das JDK 6 Update 27 (32-bit) für Windows.

Page 69: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

B Installation und Konfiguration 63

B Installation und Konfiguration

Integration des Mutation Testing Framework in ein bestehendes Refacola Projekt

Kopieren Sie die Datei mutation.annotation.refacola aus

dem Ordner .\MutationTesting\annotation

in das Paket /refacola

des Projekts de.feu.ps.refacola.lang.java.refacola.

Importieren Sie alle Projekte aus

dem Ordner .\MutationTesting\src

in Ihren Eclipse Workspace, der die Refacola Projekte enthält.

Führen Sie

im Projekt de.feu.ps.refacola.lang.java

das Script /GenerateJavaApiJava.mwe2 aus.

Bestätigen Sie eine evtl. erscheinende Fehlermeldung.

Erstellen Sie danach alle Projekte.

Konfiguration des Mutation Testing Frameworks und von Refacola für die Entwicklung unter

Eclipse

Importieren Sie alle Projekte aus

der Datei .\Refacola_MutationTesting\src\Refacola_MutationTesting_src.zip

in Ihren Eclipse Workspace.

Führen Sie

im Projekt de.feu.ps.refacola.dsl

das Script /de.feu.ps.refacola.dsl/GenerateRefacola.mwe2 aus.

Bestätigen Sie eine evtl. erscheinende Fehlermeldung.

Führen Sie anschließend

im Projekt de.feu.ps.refacola.factbase

das Script /de.feu.ps.refacola.factbase/GenerateFactBase.mwe2 aus.

Bestätigen Sie eine evtl. erscheinende Fehlermeldung.

Führen Sie dann noch

im Projekt de.feu.ps.refacola.lang.java

das Script /GenerateJavaApiJava.mwe2 aus.

Bestätigen Sie eine evtl. erscheinende Fehlermeldung.

Erstellen Sie nun alle Projekte.

Page 70: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

64 B Installation und Konfiguration

Mutation Testing Framework als Eclipse-Plugin installieren

Kopieren Sie den Inhalt

des Ordners .\Refacola_MutationTesting\release in

den Unterordner \plugins

Ihrer Eclipse Installation.

Starten Sie anschließend Eclipse neu.

Konfiguration von Eclipse

Es wird empfohlen den von Eclipse maximal zu nutzenden Arbeitsspeicher auf 512 MB oder höher

zu setzen, um OutOfMemoryErrors zu vermeiden.

Starten Sie dazu Eclipse mit folgendem Befehl:

eclipse -vmargs -Xms512m -Xmx512m -XX:PermSize=512m -XX:MaxPermSize=512m

Festlegen der maximalen Anzahl zu berechnender Lösungen pro Mutation im Mutation Testing

Framework

Die maximale Anzahl zu berechnender Lösungen pro Mutation kann im Klassen-Konstruktor des

Activators im Projekt de.feu.ps.refacola.mutation.ui eingestellt werden. Die Vorein-

stellung macht keine Beschränkung bezüglich der Anzahl zu berechnender Lösungen pro Mutation.

Page 71: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

C Benutzungsanleitung für den Refacola-Entwickler 65

C Benutzungsanleitung für den Refacola-Entwickler

Zur Auszeichnung der deklarativ definierten Constraint-Regeln verwendet das Mutation Testing Fra-

mework die in der Datei mutation.annotation.refacola vorhandene Negatable-Annotation.

Sollte die im Eclipse-Plugin de.feu.ps.refacola.mutation vorhandene Klasse

MutationCatalyst Fehler-Marker enthalten, die darauf zurückzuführen sind, dass der Typ

Negatable nicht aufgelöst werden kann, dann wurde die Java-Annotation Negatable noch nicht

generiert. Bitte stellen Sie sicher, dass sich die Datei mutation.annotation.refacola im Eclip-

se-Plugin de.feu.ps.refacola.lang.java im Paket refacola befindet und führen Sie das

MWE2-Script GenerateJavaApiJava.mwe2 im gleichnamigen Eclipse-Plugin aus.

Bevor Constraint-Regeln ausgezeichnet werden können, muss die Negatable-Annotation über die in

Listing 26 angegebene Import-Anweisung der Refacola-Datei (Ruleset.refacola), welche die

Constraint-Regeln beinhaltet, bekannt gemacht werden.

import "mutation.annotation.refacola"

Listing 26 Importieren der Negatable-Annotation

Nun können beliebige Constraint-Regeln durch anheften der Negatable-Annotation für das Mutation

Testing ausgezeichnet werden (s. Listing 27). Da das Mutation Testing Framework auf die aus den

Constraint-Regeln generierten Java-Klassen zugreift, werden Änderungen für das Framework erst

sichtbar, wenn anschließend das MWE2-Script GenerateJavaApiJava.mwe2 ausgeführt wird.

@Negatable

OOPSLA_f0_nameBasedAccess

for all

r: Java.NamedReference

E: Java.NamedEntity

do

if

Java.binds(r, E)

then

r.identifier = E.identifier

end

Listing 27 Auszeichnung einer Constraint-Regel

Während des Mutation Testing werden die von den ausgezeichneten Constraint-Regeln erzeugten

Constraints negiert, um auf diese Weise Mutanten zu generieren. Die Qualität der Mutanten sowie die

Laufzeit des Mutation Testing hängen maßgeblich von der Wahl der Constraint-Regeln ab, die ausge-

zeichnet wurden. Es wird deshalb empfohlen nur solche Constraint-Regeln mit der Negatable-

Annotation zu markieren, dessen Constraints das Bindungsverhalten des Programms sicherstellen. Ihre

Negierung kann dann dazu führen, dass die generierten Mutanten ein verändertes Programmverhalten

aufweisen.

Page 72: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

66 C Benutzungsanleitung für den Refacola-Entwickler

Es ist zu beachten, dass Constraint-Regeln nur als Ganzes ausgezeichnet werden können. Eine direkte

Selektion einzelner Constraints ist nicht möglich. Sollte dies jedoch gewünscht sein, können

Constraint-Regeln wie in Listing 28 und Listing 29 dargestellt aufgeteilt werden, um dann die resultie-

renden Constraint-Regeln einzeln auszuzeichnen. Die gezeigte Aufteilung der Constraint-Regeln hat

keinerlei Einfluss auf die generierten Constraints und berührt vorhandene Refaktorisierungen nicht.

OOPSLA_f0_memberAccess

for all

rec: Java.DeclaredTypedEntityReference

ref: Java.MemberReference

M: Java.Member

do

if

Java.binds(ref, M),

Java.receiver(ref, rec)

then

rec.owner = ref.owner,

Java.sub*(rec.inferredDeclaredType, M.owner)

end

Listing 28 Komplexe Constraint-Regel

OOPSLA_f0_memberAccess_1

for all

rec: Java.DeclaredTypedEntityReference

ref: Java.MemberReference

M: Java.Member

do

if

Java.binds(ref, M),

Java.receiver(ref, rec)

then

rec.owner = ref.owner

end

OOPSLA_f0_memberAccess_2

for all

rec: Java.DeclaredTypedEntityReference

ref: Java.MemberReference

M: Java.Member

do

if

Java.binds(ref, M),

Java.receiver(ref, rec)

then

Java.sub*(rec.inferredDeclaredType, M.owner)

end

Listing 29 Elementare Constraint-Regeln

Page 73: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

D Benutzungsanleitung für den Entwickler 67

D Benutzungsanleitung für den Entwickler

Die Mutation Testing View (s. Abbildung 14) kann nach erfolgreicher Installation über den "Show

View" Dialog von Eclipse aufgerufen werden.

Öffnen Sie dazu das Menü "Window" in der Menüleiste von Eclipse und wählen Sie im Sub-

Menü "Show View" den Eintrag "Other..." aus.

Öffnen Sie dann die Kategorie "General", in der die View als "Mutation Testing" gelistet und

auswählbar ist.

Abbildung 14 Mutation Testing View, vor Ausführung

Unter "Java Project" kann das Java-Programm ausgewählt werden, aus dem Mutanten generiert wer-

den sollen. Bitte stellen Sie sicher, dass alle Änderungen am Programm gespeichert wurden, dieses

keine Fehler enthält und somit kompilierbar ist.

Die in der Testbasis ("Test Base") enthaltenen JUnit Tests werden während des Mutation Testing ver-

wendet, um festzustellen, ob sie das geänderte Programmverhalten der Mutanten erkennen. Unterstützt

werden JUnit Tests, die auf JUnit 3.8.x und JUnit 4 basieren. Andere JUnit Tests werden ignoriert. Für

jeden generierten Mutanten wird die gesamte Testbasis, d. h. alle in ihr enthaltenen unterstützten JUnit

Tests, ausgeführt. Es wird daher empfohlen nur eine Testbasis zu verwenden, die kurz laufende Tests

enthält. Bitte stellen Sie auch bei der Testbasis sicher, dass alle Änderungen gespeichert wurden und

sie keine Fehler enthält. Darüber hinaus darf keiner der in der Testbasis enthaltenen Tests fehlschla-

gen. Dies würde das Ergebnis des Mutation Testing verfälschen. Bitte beachten Sie, dass auch eine

Testbasis verwendet werden kann, die keine Tests enthält. In diesem Fall können keine Mutanten er-

kannt werden.

Während des Mutation Testing werden viele Änderungen in kurzer Zeit am Programm durchgeführt.

Das kann zu Problemen führen, wenn Dateien des zu verwendenden Programms in einem Eclipse-

Editor geöffnet sind. Stellen Sie daher bitte sicher, dass während des Mutation Testing keine Datei des

ausgewählten Java-Programms im Editor geöffnet ist.

Page 74: Mutation Testing mit Refacola - fernuni-hagen.de · Februar 2012 Markus Grothoff . Inhalt ... wie z. B. ihre Benutzbarkeit. ... sprachen bieten durch ihre Komplexität und Strukturierung

68 D Benutzungsanleitung für den Entwickler

Über die gerade ausgeführten Operationen werden Sie während des Mutation Testing mittels der in

Eclipse vorhandenen Fortschrittsanzeigen (Fenster, s. Abbildung 15) informiert. Darüber hinaus ist es

Ihnen möglich, den Mutation Testing Durchlauf abzubrechen.

Abbildung 15 Fortschrittsanzeige während des Mutation Testing

Um die Fortschrittsanzeige in Abbildung 15 nach dem Ausblenden ("Always run in background")

beim nächsten Mutation Testing Durchlauf wieder anzeigen zu lassen, öffnen Sie im Menü "Window"

der Menüleiste von Eclipse den Eintrag "Preferences". Unter dem Punkt "General" kann die getroffene

Wahl ("Always run in background") rückgängig gemacht werden.

Nach einem Mutation Testing Durchlauf werden die Ergebnisse in der Mutation View dargestellt.

Dabei steht jeder Eintrag für einen Mutanten. Sie können über einen Doppelklick auf ein Ergebnis den

Mutanten in einem Eclipse-Editor aufrufen. Das manipulierte Programmelement wird entsprechend

hervorgehoben. Zum Vergleich kann zwischen ursprünglichen Programm und Mutanten beliebig ge-

wechselt werden. Über das Kontextmenü eines Ergebnisses kann wieder das ursprüngliche Programm

in einem Eclipse-Editor angezeigt werden.

Abbildung 16 Mutation Testing View, nach Ausführung