Tests sollen die defensive Programmierung unterstützen und sicherstellen
Defensive Programmierung schützt die Integrität des Systems zur Laufzeit.
Tests sind (meist statische) Diagnosewerkzeuge. Zur Laufzeit sind Ihre Tests nicht in Sicht. Sie sind wie ein Gerüst, mit dem eine hohe Mauer oder eine Felskuppel errichtet wurde. Sie lassen keine wichtigen Teile aus der Struktur heraus, weil Sie ein Baugerüst haben, das es während des Aufbaus hält. Sie haben ein Gerüst, das es während des Aufbaus hochhält , um das Einsetzen aller wichtigen Teile zu erleichtern .
EDIT: Eine Analogie
Was ist mit einer Analogie zu Kommentaren im Code?
Kommentare haben ihren Zweck, können aber überflüssig oder sogar schädlich sein. Wenn Sie beispielsweise den Kommentaren grundlegende Kenntnisse über den Code hinzufügen und dann den Code ändern, werden die Kommentare im besten Fall irrelevant und im schlimmsten Fall schädlich.
Nehmen wir also an, Sie stecken viel Grundwissen über Ihre Codebasis in die Tests, z. B. kann MethodA keine Null annehmen, und das Argument von MethodB muss es sein > 0
. Dann ändert sich der Code. Null ist für A jetzt in Ordnung, und B kann Werte von -10 annehmen. Die vorhandenen Tests sind jetzt funktional falsch, werden aber weiterhin bestanden.
Ja, Sie sollten die Tests zur gleichen Zeit aktualisieren, zu der Sie den Code aktualisieren. Sie sollten auch Kommentare aktualisieren (oder entfernen), während Sie den Code aktualisieren. Aber wir alle wissen, dass diese Dinge nicht immer passieren und dass Fehler gemacht werden.
Die Tests verifizieren das Verhalten des Systems. Dieses tatsächliche Verhalten ist systemimmanent und nicht testimmanent.
Was könnte möglicherweise falsch laufen?
Das Ziel in Bezug auf die Tests ist es, sich alles auszudenken, was schief gehen könnte, einen Test dafür zu schreiben, der das richtige Verhalten überprüft, und dann den Laufzeitcode so zu erstellen, dass er alle Tests besteht.
Was bedeutet, dass defensive Programmierung der Punkt ist .
TDD treibt defensive Programmierung an, wenn die Tests umfassend sind.
Mehr Tests, mehr defensive Programmierung
Wenn unvermeidlich Fehler gefunden werden, werden weitere Tests geschrieben, um die Bedingungen zu modellieren, unter denen der Fehler auftritt. Anschließend wird der Code mit dem Code zum Bestehen dieser Tests festgelegt, und die neuen Tests verbleiben in der Testsuite.
Eine gute Reihe von Tests wird sowohl gute als auch schlechte Argumente an eine Funktion / Methode übergeben und konsistente Ergebnisse erwarten. Dies bedeutet wiederum, dass die getestete Komponente Voraussetzungsprüfungen (defensive Programmierung) verwendet, um die ihr übergebenen Argumente zu bestätigen.
Im Allgemeinen ...
Wenn beispielsweise ein Nullargument für eine bestimmte Prozedur ungültig ist, besteht mindestens ein Test die Null und es wird eine Ausnahme / ein Fehler "ungültiges Nullargument" erwartet.
Mindestens ein anderer Test wird natürlich ein gültiges Argument übergeben - oder ein großes Array durchlaufen und unzählige gültige Argumente übergeben - und bestätigen, dass der resultierende Status angemessen ist.
Wenn ein Test nicht der Fall ist , dass die Null - Argument übergeben und mit der erwarteten Ausnahme schlug bekommen (und diese Ausnahme geworfen wurde , weil der Code defensiv den Zustand an sie übergeben markiert) ist , dann kann der null bis zu einer Eigenschaft einer Klasse zugeordnet beenden oder begraben in einer Art Sammlung, in der es nicht sein sollte.
Dies kann zu unerwartetem Verhalten in einem ganz anderen Teil des Systems führen, an den die Klasseninstanz übergeben wird, und zwar in einem entfernten geografischen Gebietsschema, nachdem die Software ausgeliefert wurde . Und das ist die Art von Dingen, die wir eigentlich vermeiden wollen, oder?
Es könnte noch schlimmer sein. Die Klasseninstanz mit dem ungültigen Status konnte serialisiert und gespeichert werden, um nur dann einen Fehler zu verursachen, wenn sie für eine spätere Verwendung wiederhergestellt wird. Meine Güte, ich weiß nicht, vielleicht handelt es sich um eine Art mechanisches Steuersystem, das nach einem Herunterfahren nicht neu gestartet werden kann, weil es seinen eigenen dauerhaften Konfigurationsstatus nicht deserialisieren kann. Oder die Klasseninstanz könnte serialisiert und an ein völlig anderes System übergeben werden, das von einer anderen Entität erstellt wurde, und dieses System könnte abstürzen.
Vor allem, wenn die Programmierer dieses anderen Systems nicht defensiv codierten.