Fachbereich Informatik/Mathematik 100. Fachbereichsseminar Fehlersuche und Test von Software 1. Fehler und Fehlersuche in Programmen … 2. „Klassisches“ Testen (Jamus, JUnit) 3. Testen reaktiver und verteilter Systeme (EPKfix, SaxTester) 7. Februar 2005 Hartmut Fritzsche 1. Fehler und Fehlersuche in Programmen … „Fehler“ - Abstraktionen: Globale Sicht: Ein Fehler ist jede Abweichung der tatsächlichen Ausprägung einer Qualitätseigenschaft von der vorgesehenen Sollausprägung. Korrektheit eines Programms: Ein Fehler ist jede Abweichung der Implikation von der Spezifikation (IEEE/ANSI-Standard). Strukturelle Softwaretests: Ein Fehler ist ein strukturelles Merkmal des Programmtextes, das ein fehlerhaftes Verhalten des Programms verursacht. Sprich: „Ein Programmfehler kann eine Programmausnahme (Exception) verursachen“. „Durch Testen kann man die Anwesenheit von Fehlern zeigen, nicht aber deren Abwesenheit“ E.W. Dijkstra 2 Fehler aus der „Praxis“: der Fehler, den ich zuletzt gesucht habe … (Shell-Interpreter, LV BS, 3. Februar 2005) der Fehler, an dem ich am längsten gesucht habe … (im Garbage Collection, System TULISP, 1985) ein wirklich komplizierter Fall … (D. Knuth, „Man or Boy“, 1964) 3 #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> int main(void) { char buf[80]; pid_t pid; int status; ein simpler Shell-Interpreter: das Klammerpaar ( ) wurde vergessen • fork() erzeugt Sohnprozess, in dem wird eingegebenes Kommando ausgeführt. • im Vaterprozess ist (fälschlicherweise) der Wert pid = 0 , da der Vergleichswert zugewiesen wird. Auch hier wird das eingelesene Kommando ausgeführt, waitpid wird nie erreicht. printf("%% "); while(fgets(buf,80, stdin) != NULL) { buf[strlen(buf) - 1] = 0; if ( (pid = fork()) < 0) printf("fork error"); else if (pid == 0) { execlp(buf, buf, (char *) 0); printf("command not executable: %s\n ",buf); exit(127); } if ((pid = waitpid(pid, &status, 0)) < 0) printf("waitpid error"); printf("%% "); } exit(0); /* exit(4) */ } 4 hartmut@linux:~/BSII> ./a.out % date Sam Feb 5 18:11:01 CET 2005 Sam Feb 5 18:11:01 CET 2005 hartmut@linux:~/BSII> echo $? 0 hartmut@linux:~/BSII> Analyse: • Fehler ohne Testwerkzeug ermittelt • keine Spezifikation vorhanden • manuelle Pfadanalyse (Parallelität) hartmut@linux:~/BSII> ./a.out % date Sam Feb 5 18:11:57 CET 2005 % ps PID TTY TIME CMD 2372 pts/1 00:00:00 bash 2665 pts/1 00:00:00 a.out 2667 pts/1 00:00:00 ps % hartmut@linux:~/BSII> 5 TULISP Garbage Collection: mark + sweep • typenreine Seiten – hier: pair • automatisch ausgelöstes GC Wirkung des Fehlers: In Listen tauchen falsche Elemente auf: 1 falsch verkettetes Element auf ca. 15000 richtig verkettete (keine Exception !) Analyse: • Es kann kein Testfall konstruiert werden, um den Fehler zu reproduzieren! • keine Spezifikation vorhanden • kein Testwerkzeug verfügbar • Fehlersuche mit Debugger 6 1 Zelle „pair“ 1 Page → 1 Halbwort (4 Hex) → 1020 Halbworte ≈ 15 Seiten → 15300 Zellen 81E 1 7 „Man or Boy“ ? oder: Wen interessiert noch die Parametervermittlung „call-by-name“? (Testprogramm für Algol-60-Compiler von D. Knuth, 1964) begin real procedure A(k,x1,x2,x3,x4,x5); value k; integer k; Prozedur B ist lokal zu A begin real procedure B; begin B wird an A übergeben k via call-by-value k := k - 1; B := A := A(k,B,x1,x2,x3,x4) end; if k <= 0 then A := x4 + x5 else B end; outreal(A(10,1,-1,-1,1,0)) end; Prozeduraufrufe 8 Ergebnisse bis k=23: >> ManOrBoyEnv 1 0 -2 0 Scheme (Ein LISP-Dialekt mit lexikaler Bindung): 1 0 1 -1 (define A (lambda(k x1 x2 x3 x4 x5) -10 (letrec ((B (lambda() -30 (set! K (- k 1)) -67 (A k B x1 x2 x3 x4)))) -138 (if (<= k 0)(+ (x4)(x5))(B))))) -291 -642 -1446 Aufruf: -3250 -7244 (A 10 (lambda() 1)(lambda() -1)(lambda() -1) -16065 (lambda() 1)(lambda() 0)) -35601 -78985 -175416 -389695 -865609 Referenz: H. Fritzsche, I.O. Kerner / Informatik-Spektrum 20/3 1997 -1922362 9 2. „Klassisches“ Testen Quellprogramm (verschiedene Quellsprachen) (Jamus, JUnit) Syntaxbaum Zielprogramm (verschiedene Objektsprachen) Der Syntaxbaum ist die Referenz-Struktur eines Programms (nicht das Quellprogramm!) 10 2. „Klassisches“ Testen (Jamus, JUnit) Test-Runner Quellprogramm Syntaxbaum (verschiedene Objektsprachen) (verschiedene Quellsprachen) Steuerflussgraph Visualisierung Zielprogramm Testrahmen (Testcases) 11 3. Testen reaktiver und verteilter Systeme … Systeme Transformative Systeme Reaktive Systeme „prozedural“, berechnen Ausgangsdaten, „offen“, reagieren auf ständig eintreffende Eingangsdaten, Eingangsdaten stehen zum Zeitpunkt der Aktivierung des Systems bereit das Bewegen durch einen Zustandsraum erfolgt ereignisgetrieben ( Hardware oder Software, Klimaanlage, BS, EPK, … ) 12 Anforderungsanalyse Formale Spezifikation BMBF Verbundprojekt EPK-fix,1995 -98 „Testassistenzsystem“ TASSI Generierung 13 TASSI • automatische und interaktive Testung • Testmonitor mit GUI • parametrisierte Teststrategien • Capture – Replay – Funktion • Browswn in Fehlerklassifikationen • Automatisches Prüfen Formalisierter Design-Richtlinien Testen ist ein Verfahren mit Stichprobencharakter! → Testendekriterium 14 Nutzung von Beweistechniken (z.B. SLD-/FWD-Resolution) Verifikation: Ziel eines Beweisvorganges ist es, nachzuweisen, dass die Software eine gegebene formale Spezifikation erfüllt (partielle bzw. totale Korrektheit). A if B and C and … A :- B, C, … . Wir drehen die Zielfunktion um: Beim Testen reaktiver Systeme ist es das Ziel, in erreichten Systemzuständen durch Beweise Fehler gegenüber formal spezifizierten Sachverhalten aufzudecken. „Es ist ein Fehler“ if B and C and … . Beispiel: Formale Überprüfung von Design-Richtlinien Es ist ein Fehler, wenn ein Objekt mit einer Hintergrundfarbe transitiv ein Element mit einer identischen Fontfarbe enthält 15 Beispiel: SaxTester (2003) Technologies & Application Development Testen verteilter (Web-) Anwendungen mit GUIs Kommunikation mittels XML-RPC Capure – Replay – Funktionalität Automatisches/manuelles Erstellen und Abspielen von Python-Skripts Testung von Browser-Inhalten (MS IE) über das Component Object Model (COM-Schnittstelle) (Diplomarbeit D. Linke, HTWD 2004, Medieninformatik) 16 Symbolic Model Checking Aussagen über erreichte oder zu erwartende Zustände eines Systems werden mit (temporal-) logischen Formeln beschrieben (z.B. Computational Tree Logic). Ein Model-Checker prüft Formeln gegenüber dem Modell. AX f : AG f : In jedem unmittelbaren Nachfolgezustand des aktuellen Zustands ist f erfüllt. Entlang jedes Pfades wird f in jedem Zustand erfüllt. Beispiel: Klimaanlage AG((Running & AboveDesiredTemp) → (AC | AX(AC))) AG((Running & BelowDesiredTemp) → (HEAT | AX(HEAT))) 17 Resümee • Praktisch werden kaum formale Spezifikationen (z.B. Klassen-Invarianten) unabhängig vom Programmtext verwendet. • Entwicklungswerkzeuge (z.B. Together ControlCenter) verbinden Spezifikationen mit Generierung (Syntaxbaum!) und Reverse Engineering. • Testwerkzeuge entlasten bisher den Programmierer nicht davon, Testfälle zu beschreiben, und werden es auch künftig nur begrenzt tun. Testfallbeschreibungen sind Spezifikationen! • Testen reaktiver, verteilter Systeme wird praktisch noch zu wenig unterstützt. Ansätze existieren für einzelne Programmiermodelle (z.B. COM) 18