Im ersten Teil habe ich die Grundlagen von Komponententests (auch Unit-Tests genannt) erklärt. Diese werden in der Form (und noch vielfältiger) in der Praxis auch so angewandt. Mit dem heutigen Artikel wollen wir die Praxis langsam verlassen und uns langsam in den Bereich der akademischen Forschung begeben – was nicht heißt, dass der in diesem Artikel vorgestellte Ansatz nicht auch in der Praxis angewendet wird; nur eben deutlich seltener (sollte sich unter meinen Lesern jemand befinden, der die hier beschriebene Technik tatsächlich in der Praxis anwendet, würde ich mich über eine kurze Meldung in den Kommentaren freuen).

Also, worum geht es?

Wie im letzten Artikel vorgestellt, können Tests oder Testmethoden verwendet werden, um einzelne Komponenten eines Programms auf Fehler hin zu untersuchen. Schlägt ein solcher Test fehl, liegt die Vermutung nah, dass die entsprechende Komponente einen Fehler enthält. Sind die Komponenten entsprechend klein gewählt, kann der Fehler somit relativ genau im Programmcode eingegrenzt werden. Stellen die Komponenten also etwa einzelne Methoden im Programm dar – man spricht auch von der Methodenebene als Granularität -, lässt sich ein potentieller Fehlerort auf eine einzelne Methode eingrenzen.

Soweit zumindest die Theorie. In der Praxis scheitert diese Idealvorstellung daran, dass derart feingranulare Komponenten selten komplett unabhängig von anderen getestet werden können. So rufen etwa Funktion während ihrer Abarbeitung meist auch andere Funktionen auf, die wiederum weitere Funktionen aufrufen können und so weiter. Ein fehlschlagender Test weist demzufolge lediglich darauf hin, dass irgendeine der durch ihn direkt oder indirekt aufgerufenen Funktionen einen Fehler enthält (und manchmal ist nicht einmal das sicher – aber dazu vielleicht in einem späteren Artikel mehr). Um dennoch auf Grundlage fehlschlagender Tests einen Fehlerort im Programm bestimmen zu können, kann man sich der Technik der Fehlerlokalisierung (Fault Localization in der Fachliteratur) bedienen.

Ein wichtiger Begriff in diesem Zusammenhang ist der der Abdeckungsmatrix. Wie erwähnt, werden durch einen Test in der Regel mehrere Funktionen direkt oder indirekt aufgerufen. Wird während der Testausführung überwacht, welcher Test welche Funktionen aufruft, können die so gesammelten Informationen in einer Matrix – eben der Abdeckungsmatrix – zusammengefasst werden. Diese hat üblicherweise eine Spalte für jeden ausgeführten Test und eine Zeile für jede ausgeführte Methode (oder was auch immer als Komponente aufgefasst wird; verbreitet sind Methoden und Statements). Eine “1” in einer Zelle der Matrix bedeutet, dass der Test dieser Spalte die entsprechende Methode (direkt oder indirekt) aufruft; eine “0” dementsprechend, dass der Test diese Methode nicht aufruft. Eine derartige Matrix könnte zum Beispiel so aussehen (die 0en wurden der Übersichtlichkeit halber weggelassen):

t1 t2 t3 t4
m1 1 1
m2 1 1 1
m3 1 1
m4 1 1 1

Diese Matrix sagt uns, dass der Test t1 die Methoden m1, m2 und m4 aufruft, der Test t2 die Methoden m2 und m3 und so weiter. Schlägt nun t1 fehlt, müsste sich der Fehler irgendwo in m1, m2 und m4 finden lassen – wo genau, lässt sich aus der Matrix aber natürlich nicht direkt ablesen.

Hier kommt nun allerdings die Technik der Fehlerlokalisierung – oder, genauer: die der abdeckungsbasierten Fehlerlokalisierung – ins Spiel. Schlägt nämlich mehr als ein Test fehl, kann aus dem Verhältnis aus fehlgeschlagenen und fehlerfreien Tests abgeschätzt werden, wo sich der Fehler ungefähr im Programm befinden könnte. Dazu wird mit einem Fehlerlokator ein Ranking aller aufgerufenen Methoden bestimmt; die Methoden mit der höchsten Fehlerwahrscheinlichkeit befinden sich im Ranking oben, die anderen unten.

Ein in der Forschung häufig zitierter Fehlerlokator ist der Tarantula-Lokator (über die Herkunft des Namens müsste ich spekulieren, würde aber meinen, dass er nach einer Spinne benannt wurde, weil er in den Spider Labs der University of California entwickelt wurde; letztere haben vermutlich ihren Namen daher, dass sie sich mit der Bug-Suche beschäftigen…aber das ist alles Spekulation). Dieser Lokator berechnet das Ranking, indem jeder Methode ein Verdächtigkeitswert zugeordnet wird – je verdächtiger eine Methode ist, desto wahrscheinlicher enthält sie einen Fehler. Die Verdächtigkeit einer Methode errechnet sich aus dem Verhältnis von fehlschlagenden und erfolgreichen Tests, von denen sie aufgerufen wird; konkret sieht die Formel so aus:

rT(mi) =
f(mi)
f
p(mi)
p
 + 
f(mi)
f

Die Formel sieht komplizierter aus, als sie ist; gehen wir sie daher Stück für Stück durch. Berechnet werden soll rT(mi), also der Rank der Methode mi mit Hilfe des Tarantula-Lokators rT. Der Ausdruck f(mi) bezeichnet die Menge an fehlgeschlagenen Tests, welche die Methode mi aufrufen; f bezeichnet dagegen die Menge aller Tests, die fehlschlagen. Der Term f(mi) / f bezeichnet damit das Verhältnis zwischen den fehlschlagenden Tests, die mi aufrufen, und allen fehlschlagenden Tests. Analog dazu bezeichnet p(mi) die Menge an erfolgreichen Tests, die mi aufrufen und p die Menge aller erfolgreichen Tests.

Schauen wir uns dazu ein konkretes Beispiel an. Gegeben sei die folgende Abdeckungsmatrix, wobei die mit fi bezeichneten Spalten fehlschlagende und die mit pi bezeichneten Spalten erfolgreiche Tests kennzeichnen:

f1 f2 f3 f4 | p1 p2 p3
m1 1 |
m2 1 1 | 1
m3 1 | 1 1 1
m4 1 1 | 1 1 1
   
rT
1,00
0,60
0,20
0,33

Rechts neben der Tabelle sind die Verdächtigkeitswerte zu sehen, die durch den Tarantula-Lokator berechnet wurden. m1 wird nur durch einen einzigen (fehlschlagenden) Test aufgerufen, wodurch sich für die Berechnung der Verdächtigkeit folgende Formel ergibt:

rT(m1) =
1
4
= 1
0
3
 + 
1
4

Die Methode m2 wird von 2 fehlschlagenden und einem erfolgreichen Test aufgerufen, weswegen das Ranking folgendermaßen berechnet wird:

rT(m2) =
2
4
= 0,60
1
3
 + 
2
4

Anschließend werden die Methoden entsprechend ihres Rankings sortiert, in diesem Fall also m1, m2, m4 und als letztes m3. Der Tarantula-Lokator bewertet Methoden, die von vielen fehlschlagenden und wenig erfolgreichen Tests aufgerufen werden, als größere Kandidaten für einen Fehler als Methoden, die von wenigen fehlschlagenden und vielen erfolgreichen Tests aufgerufen werden. Für die systematische Fehlersuche würde man nun beginnen, zuerst m1 nach einem Fehler zu durchsuchen, da dies die Methode mit der größten Fehlerwahrscheinlichkeit ist. Sollte sich hier ein Fehler finden, ist die Suche beendet; ansonsten würde mit m2 fortgefahren werden, dann m4 und als letztes m3 (wo dann hoffentlich ein Fehler gefunden wird). Eine Voraussetzung für den Einsatz von Tarantula ist, dass wenigstens ein Test fehlschlägt und wenigstens ein Test erfolgreich durchläuft – ansonsten würde sich in der Formel eine Division durch 0 ergeben, was ja nicht im Sinne der Mathematik ist.

Neben Tarantula gibt es noch weitere (und bessere) Lokatoren, durch welche die getesteten Methoden in ein Ranking überführt werden können. Die meisten von ihnen arbeiten über Formeln, die auf die ein oder andere Weise das Verhältnis von fehlschlagenden zu erfolgreichen Tests, die eine Methode ausführen, bewerten. Ein Problem ist allerdings allen gemein (darin liegt wohl auch begründet, dass die Fehlerlokalisierung nach wie vor ein eher akademisch bearbeitetes Thema ist): Bei hinreichend großen Programmen müssen sehr viele Methoden entsprechend ihres Rankings untersucht werden, bis ein Fehler gefunden wird – zu viele, um in der Praxis gegen den “intuitiven Ansatz” (d. h. einen fehlschlagenden Test debuggen und den Fehler auf Grundlage der Kenntnisse über das Programm suchen) zu bestehen.

Weiterhin ist problematisch, dass dieser Ansatz nur für das Finden eines einzelnen Fehlers geeignet ist. Warum das so ist und welche Lösung es hierfür gibt, werde ich im nächsten Teil der Serie beleuchten.

Kommentare (12)

  1. #1 Eckbert
    Juli 23, 2017

    Interessantes Thema und gute Erklärung! Weiter so! 🙂

  2. #2 tomtoo
    Juli 23, 2017

    Nicht böse gemeint ,aber gerade wurde ein neues OS geprogt und die Fehler wurden nur über anwenderfeedback beseitigt.

    Aber die anderen sind immerhin schon bei 0.1 ihrer Unittests. Das wird !

    • #3 Marcus Frenkel
      Juli 23, 2017

      @tomtoo: So ganz kann ich mit dem Kommentar jetzt nichts anfangen…etwas Erklärung vielleicht?

  3. #4 tomtoo
    Juli 23, 2017

    Sry ist so rausgerutscht. Schöne Theorie. Unittests kosten Zeit. Prog. Aufwand auch mit Tools. Ich kenne mich aber mit hoch optimierten OOP Systemen nicht aus. Also beim Change Management im regulierten Umfeld usw. Alltag war anwendertest.Der unterschreibt. Und zahlt für Weiterentwicklung. Sieht der nix. Keine Kohle und wenn das System noch so schön ist. Aber gut ist ja nur eigene Erfahrung.

  4. #5 tomtoo
    Juli 23, 2017

    Entschuldige ist so meine Art. Nicht böse gemeint.
    Hab ich sowas wie ein reines SAP System ist ja noch vorstellbar , die Sinnhaftigkeit von Unittests. Aber gehts mal über mehr Welten z. B. SAP ..SQL.. .DataTransformation…Analisys..Web. Wie soll das noch gehen ?
    Sollte keine Werbung sein. Aber Alltag halt.

  5. #6 tomtoo
    Juli 23, 2017

    Noch ein Nachtrag sry. Hatten mal einen wunderschönen Fehler einer CRM Software. Hatt 24.00 Uhr geliefert. Streikt der SQL Server beim INSERT : ) da wär ein Unittest super gewesen.

  6. #7 Dwon
    Juli 24, 2017

    Fehlerlokalisierung ist in Java glücklicherweise einfacher. Mit dem StackTrace (falls vorhanden) die Methoden abklappern. Und ein vernünftiges Logging dazu, schon kann man sehr stark eingrenzen wo der Fehler liegt.
    Oder man schaut sich im Debug-Modus live an welche Werte die Variablen gerade haben.

    Gibt es zur obigen Methode auch ein Praxisbeispiel? Oder geht es um große Systeme, die aus verschiedener Software bestehen?

    • #8 Marcus Frenkel
      Juli 24, 2017

      Unabhängig von der Programmgröße: Der StackTrace hilft nur bedingt, da er immer noch erfordert, dass mehrere Methoden betrachtet werden, bis der Fehler gefunden wird. Die Idee der Fehlerlokalisierung ist, dass idealerweise durch den Lokator genau die Methode “berechnet” wird, welche den Fehler enthält, der Programmierer also überhaupt nicht nach einer fehlerhaften Methode (oder einem fehlerhaften Statement; das ganze kann ja auch auf Statementebene durchgeführt werden) suchen muss.

  7. #9 Robert
    Juli 24, 2017

    tomtoo,
    …..Fehlersuche,
    ganz teuflisch wird es, wenn man den Fehler lokalisiert hat aber nicht weiß, warum der Programmteil nicht so arbeitet, wie er soll.
    Aus meinem kleinen Erfahrungsschatz kann ich nur sagen, manchmal geht es schneller, wenn man ein Programmteil ganz neu schreibt, anders schreibt, als zu debuggen.

  8. #10 tomtoo
    Juli 24, 2017

    @Robert
    Nö richtig teuflisch ist zu sehen das was nicht stimmt. Es im Hirn aber nicht klick macht. Wie die besagten 24:00 ; )

  9. #11 Robert
    Juli 25, 2017

    tomtoo,
    …..es nicht klick macht,
    das meine ich doch, das frisst doch die Zeit.

    Dwon,
    …..Debugger,
    die sind eine herrliche Sache. Bei Syntaxfehlern geht es einfach prima. Aber wenn du einen logischen Fehler im Programm hast, dann hilft dir kein Schwein.
    Deswegen habe ich einmal ein ganzes Programm umstrukturieren müssen.

  10. #12 Laie
    August 5, 2017

    Derzeit sucht man Softwarefehler bei VW, die gute Abgase ausgaben, obwohl die Werte schlecht waren. Anwenderfeedback reicht da nicht aus, die mussten erst verklagt werden, damit die Feedbackschleife so einigermassen funktioniert. Vielleicht sind es auch Fachkräfte die mängeln?