Ich kann nicht auf eine gute Online-Ressource verweisen (die englischen Wikipedia-Artikel zu diesen Themen sind in der Regel verbesserungsfähig), aber ich kann eine Vorlesung zusammenfassen, die auch grundlegende Testtheorien behandelte.
Testmodi
Es gibt verschiedene Testklassen, wie Unit-Tests oder Integrationstests . Ein Komponententest bestätigt, dass ein zusammenhängendes Stück Code (Funktion, Klasse, Modul), das für sich genommen erstellt wurde, wie erwartet funktioniert, während ein Integrationstest bestätigt, dass mehrere solcher Teile korrekt zusammenarbeiten.
Ein Testfall ist eine bekannte Umgebung, in der ein Teil des Codes ausgeführt wird, z. B. durch Verwendung einer bestimmten Testeingabe oder durch Verspotten anderer Klassen. Das Verhalten des Codes wird dann mit dem erwarteten Verhalten verglichen, z. B. einem bestimmten Rückgabewert.
Ein Test kann nur das Vorhandensein eines Fehlers beweisen, niemals das Fehlen aller Fehler. Tests setzen eine Obergrenze für die Programmkorrektheit fest.
Code-Abdeckung
Um Kennzahlen für die Codeabdeckung zu definieren, kann der Quellcode in ein Kontrollflussdiagramm übersetzt werden, in dem jeder Knoten ein lineares Codesegment enthält. Die Steuerung fließt nur am Ende jedes Blocks zwischen diesen Knoten und ist immer bedingt (wenn Bedingung, dann gehe zu Knoten A, sonst gehe zu Knoten B). Der Graph hat einen Startknoten und einen Endknoten.
- Mit diesem Diagramm wird die Anweisungsabdeckung dargestellt das Verhältnis aller besuchten Knoten zu allen Knoten. Eine vollständige Berichterstattung ist für gründliche Tests nicht ausreichend.
- Zweigabdeckung ist das Verhältnis aller besuchten Kanten zwischen Knoten in der CFG zu allen Kanten. Dadurch werden Schleifen nicht ausreichend getestet.
- Die Pfadabdeckung ist das Verhältnis aller besuchten Pfade zu allen Pfaden, wobei ein Pfad eine beliebige Folge von Kanten vom Anfang bis zum Endknoten ist. Das Problem ist, dass es bei Schleifen unendlich viele Pfade geben kann, sodass die vollständige Pfadabdeckung praktisch nicht getestet werden kann.
Es ist daher oft nützlich, die Bedingungsüberdeckung zu überprüfen .
- In der einfachen Bedingungsabdeckung ist jede atomare Bedingung einmal wahr und einmal falsch - dies garantiert jedoch keine vollständige Bedingungsabdeckung.
- In der Mehrfachbedingungsabdeckung haben die atomaren Bedingungen alle Kombinationen von
true
und angenommen false
. Dies setzt eine vollständige Zweigabdeckung voraus, ist jedoch ziemlich teuer. Das Programm kann zusätzliche Einschränkungen haben, die bestimmte Kombinationen ausschließen. Diese Technik ist gut, um eine Zweigabdeckung zu erhalten, kann toten Code finden, kann jedoch keine Fehler finden, die von der falschen Seite stammen Zustand zurückzuführen sind.
- In der minimalen Mehrfachbedingungsabdeckung ist jede atomare und zusammengesetzte Bedingung einmal wahr und falsch. Dies impliziert immer noch eine vollständige Zweigabdeckung. Es handelt sich um eine Teilmenge der Mehrfachbedingungsabdeckung, die jedoch weniger Testfälle erfordert.
Bei der Erstellung von Testeingaben unter Verwendung der Bedingungsabdeckung sollte der Kurzschluss berücksichtigt werden. Beispielsweise,
function foo(A, B) {
if (A && B) x()
else y()
}
muss mit getestet werden foo(false, whatever)
, foo(true, false)
undfoo(true, true)
für eine vollständige minimale Abdeckung mehrerer Zustände.
Wenn Sie Objekte haben, die sich in mehreren Zuständen befinden können, ist es sinnvoll, alle Zustandsübergänge analog zu Kontrollabläufen zu testen.
Es gibt einige komplexere Kennzahlen für die Abdeckung, sie ähneln jedoch im Allgemeinen den hier dargestellten Kennzahlen.
Dies sind White-Box- Testmethoden, die teilweise automatisiert werden können. Beachten Sie, dass eine Unit-Test-Suite eine hohe Codeabdeckung durch eine ausgewählte Metrik anstreben sollte, jedoch nicht immer 100%. Es ist besonders schwierig, die Ausnahmebehandlung zu testen, wenn Fehler an bestimmten Stellen injiziert werden müssen.
Funktionstests
Dann gibt es Funktionstests, die bestätigen, dass der Code der Spezifikation entspricht, indem die Implementierung als Black Box betrachtet wird. Solche Tests sind sowohl für Komponententests als auch für Integrationstests nützlich. Da es unmöglich ist, mit allen möglichen Eingabedaten zu testen (z. B. die Stringlänge mit allen möglichen Strings zu testen), ist es nützlich, die Eingabe (und Ausgabe) in äquivalente Klassen zu gruppieren - wenn dies length("foo")
korrekt foo("bar")
ist, funktioniert dies wahrscheinlich auch. Für jede mögliche Kombination zwischen Eingangs- und Ausgangsäquivalenzklassen wird mindestens ein repräsentativer Eingang ausgewählt und getestet.
Man sollte zusätzlich testen
- Grenzfälle
length("")
, foo("x")
, length(longer_than_INT_MAX)
,
- Werte, die von der Sprache, aber nicht vom Vertrag der Funktion zugelassen sind
length(null)
, und
- mögliche Junk-Daten
length("null byte in \x00 the middle")
...
Bei numerischen Werten bedeutet dies Testen 0, ±1, ±x, MAX, MIN, ±∞, NaN
und bei Gleitkommavergleichen Testen von zwei benachbarten Gleitkommazahlen. Als weitere Ergänzung können zufällige Testwerte aus den Äquivalenzklassen ausgewählt werden. Um das Debuggen zu vereinfachen, lohnt es sich, den verwendeten Startwert aufzuzeichnen.
Nichtfunktionale Tests: Belastungstests, Stresstests
Eine Software hat nicht-funktionale Anforderungen, die ebenfalls getestet werden müssen. Dazu gehört das Testen an den definierten Grenzen (Lasttests) und darüber hinaus (Stresstests). Bei einem Computerspiel kann dies bedeuten, dass in einem Auslastungstest eine Mindestanzahl von Bildern pro Sekunde festgelegt wird. Eine Website wird möglicherweise einem Stresstest unterzogen, um die Antwortzeiten zu ermitteln, wenn doppelt so viele Besucher wie erwartet die Server beschädigen. Solche Tests sind nicht nur für ganze Systeme relevant, sondern auch für einzelne Entitäten. Wie verschlechtert sich eine Hash-Tabelle mit einer Million Einträgen?
Andere Arten von Tests sind Tests des gesamten Systems, bei denen Szenarien simuliert werden, oder Abnahmetests zum Nachweis, dass der Entwicklungsvertrag erfüllt wurde.
Nicht-Testmethoden
Bewertungen
Es gibt nicht testende Techniken, die zur Qualitätssicherung eingesetzt werden können. Beispiele sind exemplarische Vorgehensweisen, formale Codeüberprüfungen oder Paarprogrammierung. Während einige Teile automatisiert werden können (z. B. durch Verwendung von Linters), sind diese im Allgemeinen zeitintensiv. Codeüberprüfungen durch erfahrene Programmierer weisen jedoch eine hohe Fehlerentdeckungsrate auf und sind besonders während des Entwurfs nützlich, wenn keine automatisierten Tests möglich sind.
Warum schreiben wir immer noch Tests, wenn Code-Reviews so gut sind? Der große Vorteil von Testsuiten besteht darin, dass sie (meistens) automatisch ausgeführt werden können und daher für Regressionstests sehr nützlich sind .
Formale Überprüfung
Die formale Verifizierung geht und beweist bestimmte Eigenschaften des Codes. Die manuelle Überprüfung ist vor allem für kritische Teile und weniger für ganze Programme sinnvoll. Beweise setzen der Programmkorrektheit eine Untergrenze . Proofs können bis zu einem gewissen Grad automatisiert werden, z. B. über eine statische Typprüfung.
Bestimmte Invarianten können mit assert
Anweisungen explizit überprüft werden .
Alle diese Techniken haben ihren Platz und ergänzen sich. TDD schreibt die Funktionstests im Voraus, aber die Tests können anhand ihrer Abdeckungsmetriken beurteilt werden, sobald der Code implementiert ist.
Das Schreiben von testbarem Code bedeutet das Schreiben kleiner Codeeinheiten, die separat getestet werden können (Hilfsfunktionen mit geeigneter Granularität, Prinzip der Einzelverantwortung). Je weniger Argumente jede Funktion benötigt, desto besser. Ein solcher Code eignet sich auch zum Einfügen von Scheinobjekten, z. B. durch Abhängigkeitsinjektion.
double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }
das ich von meinem Mathematiklehrer gelernt habe . Dieser Code hat genau eine Lücke , die allein durch Black-Box-Tests nicht automatisch erkannt werden kann. In der Mathematik gibt es kein solches Loch. Im Kalkül dürfen Sie das Loch schließen, wenn die einseitigen Grenzen gleich sind.