Es wird angenommen, dass Unit-Tests nicht erforderlich sind. Dass nur die halbe Wahrheit in ihnen verborgen ist. Und diese echten Informationen über das Verhalten des Programms werden nur dann enthüllt, wenn wir sie in einem Integrationstest sammeln.
Es gibt einen Grund dafür, aber sind Unit-Tests wirklich unvollständig und können sie zuverlässiger gemacht werden? Wie viele Gründe für ihre Unvollständigkeit?
Angenommen, wir haben zwei Komponenten, die durch Unit-Tests abgedeckt sind: Caller und Callee. Der Aufrufer ruft Callee mit einem Argument auf und verwendet irgendwie das zurückgegebene Objekt. Jede der Komponenten hat ihre eigenen Abhängigkeiten, die wir einweichen.
Wie viele Szenarien, in denen sich diese Komponenten während der Integration unerwartet verhalten?
Das erste Szenario ist ein
Problem außerhalb beider Komponenten . Zum Beispiel hängen beide vom Status der Datenbank, der Autorisierung, Umgebungsvariablen, globalen Variablen, Cookies, Dateien usw. ab. Dies zu beurteilen ist recht einfach, da selbst in sehr großen Programmen normalerweise nur eine begrenzte Anzahl solcher Streitpunkte vorhanden ist.
Das Problem kann natürlich entweder durch eine Neugestaltung mit reduzierten Abhängigkeiten gelöst werden.
oder wir simulieren direkt einen möglichen Fehler in einem Szenario der obersten Ebene, dh wir führen die CallingStrategy-Komponente (OffendingCaller, OffendedCallee) {} ein und simulieren den Callee-Absturz und die Fehlerbehandlung in CallingStrategy. Hierzu sind keine Integrationstests erforderlich, es ist jedoch ein Verständnis erforderlich, dass das spezifische Verhalten einer der Komponenten ein Risiko für die andere Komponente darstellt, und es wäre gut, dieses Szenario in eine Komponente zu isolieren.
Zweites Szenario: Das Problem liegt in der Schnittstelle integrierbarer Objekte, d.h.
Ein unnötiger Zustand eines der Objekte ist in ein anderes durchgesickert .
In der Tat ist dies ein Fehler in der Schnittstelle, der dies ermöglicht. Die Lösung des Problems liegt ebenfalls auf der Hand - Typisierung und Verengung von Schnittstellen, frühzeitige Validierung von Parametern.
Wie wir sehen können, sind beide Gründe äußerst alltäglich, aber es wäre schön, klar zu artikulieren, dass es keine anderen gibt.
Wenn wir also unsere Klassen auf 1) internen Zustand und 2) externe Abhängigkeiten überprüft haben, haben wir keinen Grund, an der Zuverlässigkeit von Komponententests zu zweifeln.
(Irgendwo in der Ecke weint ein funktionierender Programmierer leise mit den Worten "Ich habe es dir gesagt", aber momentan nicht darüber).
Aber wir können einfach irgendeine Sucht vergessen oder verpassen!
Kann grob geschätzt werden. Angenommen, jede Komponente enthält zehn Szenarien. Wir überspringen ein Szenario von zehn. Beispielsweise gibt Callee plötzlich null zurück und Caller erhält plötzlich eine NullPointerException. Wir müssen zweimal einen Fehler machen, was bedeutet, dass die Wahrscheinlichkeit, irgendwohin zu fallen, 1/100 beträgt. Es ist schwer vorstellbar, dass das Integrationsszenario für die beiden Elemente dies erfassen wird. Bei vielen nacheinander als Komponenten innerhalb des Integrationstests bezeichneten Komponenten steigt die Wahrscheinlichkeit, einige der Fehler abzufangen. Je länger der Stapel des Integrationstests ist und je mehr Szenarien, desto gerechtfertigter ist er.
(Die wahre Mathematik der Fehlerakkumulation ist natürlich viel komplizierter, aber das Ergebnis variiert nicht sehr).
Bei der Durchführung des Integrationstests können wir jedoch einen signifikanten Anstieg des Rauschens aufgrund unterbrochener Abhängigkeiten und einen erheblichen Zeitaufwand für das Auffinden eines Fehlers erwarten. Diese sind auch proportional zur Länge des Stapels.
Das heißt, es stellt sich heraus, dass
Integrationstests erforderlich sind, wenn Unit-Tests schlecht sind oder fehlen . Zum Beispiel, wenn in jedem Komponententest nur ein gültiges Skript überprüft wird, wenn sie zu breite Schnittstellen verwenden und keine allgemeinen Abhängigkeiten analysieren.