Unit-Tests sind ja hinreichend bekannt. Die nachfolgend vorgestellten Testverfahren sicher jedoch weniger. Deshalb gibts heut mal einen kleinen Ausflug. Ohne viel Vorgeplänkel stelle ich verschiedene überdeckungsorientierte Testverfahren vor.
Zeilenüberdeckung
Folgender (sinnloser) Code:
function foo($bar) { if ($bar && !$bar) return true; return false; }
Was fällt auf? Die Funktion kann nie true returnen. Durch einen Test auf Zeilenabdeckung kann also erkannt werden, dass hier irgendwas nicht stimmt, weil die return true; – Zeile niemals erreicht wird. Allerdings (großes allerdings!), würde folgende „Umstellung“ des Codes bereits zu einer 100%igen Zeilenüberdeckung führen:
function foo($bar) { if ($bar && !$bar) return true; return false; }
Klasse, oder? Einfach das return true in die selbe Zeile geklatscht und schon wird diese Zeile ausgeführt. Auch wenn man mit dem Codesniffer argumentieren könnte, kann die Zeilenüberdeckung also unmöglich das gelbe vom Ei sein.
Anweisungsüberdeckung
Die Anweisungsüberdeckung löst das Problem der Zeilenüberdeckung, indem geprüft wird, ob alle Anweisungen im Code mindestens einmal ausgeführt werden. So würde sich das return true aus dem Beispiel oben nicht mehr verstecken können, selbst wenn es in der gleichen Zeile steht – es ist und bleibt eine Anweisung, die nie ausgeführt wird. Damit lässt sich also schon einiges an totem Code erkennen.
function doStuff() { print "Doing stuff"; return; veryComplexFunction(); $foo = new Bar(); return $foo; }
Der Code wird immer sinnvoller, gell? Prinzip sollte aber klarwerden: Alles unterhalb des ersten returns wird nie erreicht, weswegen die letzten 3 Anweisungen in der Funktion gnadenlos durch den Anweisungsüberdeckungs-Test auffallen.
Was ist das Problem mit der Anweisungsüberdeckung?
function helloworld($hello) { if ($hello == "hello") { print "Hello World"; return true; } }
Bei obigem Code ist die Anweisungsüberdeckung zufrieden: Es können alle Anweisungen erreicht werden. Nur: Was ist, wenn der Parameter $hello nicht „hello“ ist? Also: Was ist mit dem leeren else-Zweig?
Zweigüberdeckung
Die Zweigüberdeckung löst dieses Problem: Alle Zweige des Programms müssen durchlaufen werden. Heißt: Zu jedem „if“ gibt es auch ein else – selbst wenn es im Code nicht explizit angegeben ist. Damit subsumiert die Zweigüberdeckung die Anweisungsüberdeckung. Dabei kann die Zweigüberdeckung ziemlich aufwändig zu testen sein, denn if’s in Schleifen werden z.B. schnell sehr aufwändig zu testen und erfordern oft das Einschleusen von boolschen Variablen, um das Abbiegen in einen gewissen Zweig zu erzwingen.
Pfadüberdeckung
Noch nicht komplex genug? Kein Problem! Die Pfadüberdeckung stellt wohl die Krönung dar. Es muss jeder mögliche Pfad durch das Programm von Anfang bis Ende begangen werden.
Das Bild zeigt, wieviele Varianten es gibt, allein eine einzige Funktion zu durchlaufen (Schleifen sind der Tod!).
That’s it
In der Praxis muss man ohnehin selbst entscheiden, welchen Aufwand man betreiben möchte. Finde es aber ganz angenehm, die verschiedenen Verfahren immer im Hinterkopf zu haben.
Bildquelle der ersten beiden Bilder: Wikipedia: Kontrollflussorientierte Testverfahren (ohnehin sehr empfehlenswerter Artikel), Bildquelle des letzten Bildes: Script des geschätzten Profs Ralf Hahn.
Ganz interessanter Einblick, danke dafür. Mir kam hier aber die Pfadüberdeckung bisserl zu kurz.
Was ist mit der Bedinungsüberdeckung? ;-)