Wenn ich Unit-Tests auf die "richtige" Art und Weise durchführe, dh jeden öffentlichen Anruf stoppe und voreingestellte Werte oder Mocks zurückgebe, habe ich das Gefühl, dass ich eigentlich nichts teste. Ich sehe mir meinen Code buchstäblich an und erstelle Beispiele, die auf dem Fluss der Logik durch meine öffentlichen Methoden basieren. Und jedes Mal, wenn sich die Implementierung ändert, muss ich diese Tests erneut ändern, ohne wirklich das Gefühl zu haben, dass ich etwas Nützliches erreiche (sei es mittel- oder langfristig). Ich mache auch Integrationstests (einschließlich Non-Happy-Paths) und die erhöhten Testzeiten stören mich nicht wirklich. Bei denen habe ich das Gefühl, dass ich tatsächlich auf Regressionen teste, weil sie mehrere abgefangen haben, während diese Unit-Tests mir nur zeigen, dass sich die Implementierung meiner öffentlichen Methode geändert hat, die ich bereits kenne.
Unit Testing ist ein riesiges Thema, und ich habe das Gefühl, dass ich hier etwas nicht verstehe. Was ist der entscheidende Vorteil von Unit-Tests gegenüber Integrationstests (ohne Zeitaufwand)?
Antworten:
Dies hört sich so an, als ob die zu testende Methode mehrere andere Klasseninstanzen benötigt (die Sie verspotten müssen) und mehrere Methoden selbst aufruft.
Diese Art von Code ist aus den von Ihnen genannten Gründen in der Tat schwer zu testen.
Was ich als hilfreich empfunden habe, ist die Aufteilung solcher Klassen in:
Dann sind die Klassen von 1. einfach zu testen, da sie nur Werte akzeptieren und ein Ergebnis zurückgeben. In komplexeren Fällen müssen diese Klassen möglicherweise selbst Aufrufe ausführen, sie rufen jedoch nur Klassen ab 2. auf (und rufen nicht direkt z. B. eine Datenbankfunktion auf), und die Klassen ab 2. sind einfach zu verspotten (weil nur sie) Legen Sie die Teile des verpackten Systems frei, die Sie benötigen.
Die Klassen von 2. und 3. können normalerweise nicht sinnvoll auf ihre Einheit getestet werden (da sie für sich genommen nichts Nützliches tun, sondern nur "Kleber" -Code sind). OTOH, diese Klassen sind in der Regel relativ einfach (und nur wenige), daher sollten sie durch Integrationstests angemessen abgedeckt werden.
Ein Beispiel
Eine Klasse
Angenommen, Sie haben eine Klasse, die einen Preis aus einer Datenbank abruft, einige Rabatte anwendet und dann die Datenbank aktualisiert.
Wenn Sie dies alles in einer Klasse haben, müssen Sie DB-Funktionen aufrufen, die schwer zu verspotten sind. Im Pseudocode:
Alle drei Schritte benötigen DB-Zugriff, daher viel (komplexes) Mocking, das wahrscheinlich kaputt geht, wenn sich der Code oder die DB-Struktur ändern.
Aufteilen
Sie haben in drei Klassen aufgeteilt: PriceCalculation, PriceRepository, App.
PriceCalculation führt nur die eigentliche Berechnung durch und erhält die benötigten Werte. App verbindet alles miteinander:
Dieser Weg:
Schließlich kann sich herausstellen, dass PriceCalculation seine eigenen Datenbankaufrufe durchführen muss. Zum Beispiel, weil nur PriceCalculation weiß, welche Daten benötigt werden, sodass sie von App nicht im Voraus abgerufen werden können. Anschließend können Sie eine auf die Anforderungen von PriceCalculation zugeschnittene Instanz von PriceRepository (oder einer anderen Repository-Klasse) übergeben. Diese Klasse muss dann verspottet werden, dies ist jedoch einfach, da die Schnittstelle von PriceRepository einfach ist, z
PriceRepository.getPrice(articleNo, contractType)
. Am wichtigsten ist, dass die PriceRepository-Schnittstelle PriceCalculation von der Datenbank isoliert, sodass Änderungen am DB-Schema oder an der Datenorganisation wahrscheinlich nicht zu einer Änderung der Schnittstelle und damit zu einer Unterbrechung der Mocks führen.quelle
Das ist eine falsche Zweiteilung.
Unit-Tests und Integrationstests dienen zwei ähnlichen, aber unterschiedlichen Zwecken. Der Zweck von Unit-Tests ist es, sicherzustellen, dass Ihre Methoden funktionieren. In der Praxis stellen die Komponententests sicher, dass der Code den in den Komponententests festgelegten Vertrag erfüllt. Dies zeigt sich in der Art und Weise, in der Komponententests entworfen wurden: Sie geben speziell an, was der Code tun soll, und versichern, dass der Code dies tut.
Integrationstests sind unterschiedlich. Integrationstests üben die Interaktion zwischen Softwarekomponenten aus. Es können Softwarekomponenten vorhanden sein, die alle ihre Tests bestehen und trotzdem Integrationstests nicht bestehen, weil sie nicht ordnungsgemäß interagieren.
Wenn Unit-Tests jedoch einen entscheidenden Vorteil haben, ist dies der Fall : Unit-Tests sind viel einfacher einzurichten und erfordern viel weniger Zeit und Aufwand als Integrationstests. Bei richtiger Anwendung fördern Komponententests die Entwicklung von "testbarem" Code, was bedeutet, dass das Endergebnis zuverlässiger, verständlicher und leichter zu warten ist. Testbarer Code weist bestimmte Merkmale auf, z. B. eine kohärente API und wiederholbares Verhalten, und gibt Ergebnisse zurück, die leicht zu bestätigen sind.
Integrationstests sind schwieriger und kostenintensiver, da Sie häufig aufwändige Verspottungen, komplexe Einstellungen und schwierige Behauptungen benötigen. Stellen Sie sich vor, Sie versuchen auf der höchsten Ebene der Systemintegration die menschliche Interaktion in einer Benutzeroberfläche zu simulieren. Ganze Softwaresysteme widmen sich dieser Art von Automatisierung. Und es ist die Automatisierung, die wir suchen. Tests am Menschen sind nicht wiederholbar und lassen sich nicht wie automatisierte Tests skalieren.
Schließlich gibt der Integrationstest keine Garantie für die Codeabdeckung. Wie viele Kombinationen von Codeschleifen, Bedingungen und Zweigen testen Sie mit Ihren Integrationstests? Weißt du es wirklich? Es gibt Tools, die Sie für Komponententests und getestete Methoden verwenden können und die Ihnen Aufschluss über die Codeabdeckung und die zyklomatische Komplexität Ihres Codes geben. Wirklich gut funktionieren sie aber nur auf Methodenebene, wo Unit-Tests leben.
Wenn sich Ihre Tests bei jedem Refactor ändern, ist das ein anderes Problem. Unit - Tests sollen über sein dokumentieren , was eine Software macht, was beweist , dass es das tut, und dann beweisen , dass es das tut wieder , wenn Sie die zugrunde liegende Implementierung Refactoring. Wenn sich Ihre API ändert oder Sie Ihre Methoden aufgrund einer Änderung im Systemdesign ändern müssen, ist dies das, was passieren soll. Wenn es häufig vorkommt, sollten Sie zuerst Ihre Tests schreiben, bevor Sie Code schreiben. Dies zwingt Sie, über die Gesamtarchitektur nachzudenken und Code mit der bereits eingerichteten API zu schreiben.
Wenn Sie viel Zeit damit verbringen, Komponententests für Trivialcode zu schreiben, wie z
dann sollten Sie Ihren Ansatz überprüfen. Unit-Tests sollen das Verhalten testen , und es gibt kein Verhalten in der obigen Codezeile. Sie haben jedoch irgendwo eine Abhängigkeit in Ihrem Code erstellt, da diese Eigenschaft mit ziemlicher Sicherheit an anderer Stelle in Ihrem Code referenziert wird. Schreiben Sie stattdessen Methoden, die die erforderliche Eigenschaft als Parameter akzeptieren:
Jetzt hat Ihre Methode keine Abhängigkeiten von etwas außerhalb von sich selbst und ist jetzt testbarer, da sie vollständig in sich geschlossen ist. Zugegeben, Sie werden dies nicht immer tun können, aber es verschiebt Ihren Code in die Richtung, testbarer zu sein, und dieses Mal schreiben Sie einen Unit-Test für das tatsächliche Verhalten.
quelle
Die Unit-Tests mit Mocks sollen sicherstellen, dass die Implementierung der Klasse korrekt ist. Sie verspotten die öffentlichen Schnittstellen der Abhängigkeiten des Codes, den Sie testen. Auf diese Weise haben Sie die Kontrolle über alles, was außerhalb der Klasse liegt, und können sicher sein, dass ein fehlgeschlagener Test auf etwas innerhalb der Klasse und nicht auf eines der anderen Objekte zurückzuführen ist.
Sie testen auch das Verhalten der getesteten Klasse und nicht die Implementierung. Wenn Sie den Code überarbeiten (neue interne Methoden usw. erstellen), sollten die Komponententests nicht fehlschlagen. Wenn Sie jedoch die öffentliche Methode ändern, sollten die Tests auf jeden Fall fehlschlagen, da Sie das Verhalten geändert haben.
Es klingt auch so, als würden Sie die Tests schreiben, nachdem Sie den Code geschrieben haben. Versuchen Sie stattdessen, die Tests zuerst zu schreiben. Versuchen Sie, das Verhalten der Klasse zu skizzieren, und schreiben Sie dann die Mindestmenge an Code, damit die Tests bestanden werden.
Sowohl Unit-Tests als auch Integrationstests sind nützlich, um die Qualität Ihres Codes sicherzustellen. Die Komponententests untersuchen jede Komponente für sich. Und die Integrationstests stellen sicher, dass alle Komponenten richtig interagieren. Ich möchte beide Typen in meiner Testsuite haben.
Unit-Tests haben mir bei meiner Entwicklung geholfen, da ich mich jeweils auf einen Teil der Anwendung konzentrieren kann. Verspotten der Komponenten, die ich noch nicht gemacht habe. Sie eignen sich auch hervorragend zur Regression, da sie alle Fehler in der von mir gefundenen Logik dokumentieren (sogar in den Unit-Tests).
AKTUALISIEREN
Das Erstellen eines Tests, der nur sicherstellt, dass Methoden aufgerufen werden, hat den Wert, dass sichergestellt wird, dass die Methoden tatsächlich aufgerufen werden. Insbesondere wenn Sie Ihre Tests zuerst schreiben, haben Sie eine Checkliste der Methoden, die durchgeführt werden müssen. Da dieser Code so ziemlich prozedural ist, müssen Sie nicht viel überprüfen, außer dass die Methoden aufgerufen werden. Sie schützen den Code für zukünftige Änderungen. Wenn Sie eine Methode vor der anderen aufrufen müssen. Oder dass eine Methode immer aufgerufen wird, auch wenn die ursprüngliche Methode eine Ausnahme auslöst.
Der Test für diese Methode wird möglicherweise nie oder nur dann geändert, wenn Sie die Methoden ändern. Warum ist das eine schlechte Sache? Es hilft, die Tests zu verstärken. Wenn Sie einen Test nach dem Ändern des Codes reparieren müssen, werden Sie die Gewohnheit haben, die Tests mit dem Code zu ändern.
quelle
Ich hatte eine ähnliche Frage - bis ich die Leistungsfähigkeit von Komponententests entdeckte. Kurz gesagt, sie sind die gleichen wie Unit-Tests, mit der Ausnahme, dass Sie nicht standardmäßig verspotten, sondern echte Objekte verwenden (idealerweise über die Abhängigkeitsinjektion).
Auf diese Weise können Sie schnell zuverlässige Tests mit einer guten Codeabdeckung erstellen. Sie müssen Ihre Mocks nicht ständig aktualisieren. Es mag ein bisschen ungenauer sein als Unit-Tests mit 100% igen Verspottungen, aber die Zeit und das Geld, die Sie sparen, kompensieren das. Das einzige, wofür Sie wirklich Mocks oder Fixtures verwenden müssen, sind Speicher-Backends oder externe Services.
Tatsächlich ist exzessives Verspotten ein Anti-Pattern: TDD Anti-Patterns und Mocks sind böse .
quelle
Obwohl die Operation bereits eine Antwort markiert hat, füge ich hier nur meine 2 Cent hinzu.
Und auch als Antwort auf
Es gibt ein hilfreiches, aber nicht genau das, wonach OP gefragt hat:
Unit Tests funktionieren, aber es gibt immer noch Fehler?
Aufgrund meiner geringen Erfahrung mit Testsuiten verstehe ich, dass Unit-Tests immer die grundlegendsten Funktionen einer Klasse auf Methodenebene testen. Meiner Meinung nach verdient jede Methode, ob öffentlich, privat oder intern , einen eigenen Komponententest. Sogar in meiner jüngsten Erfahrung hatte ich eine öffentliche Methode, die eine andere kleine private Methode aufrief. Es gab also zwei Ansätze:
Wenn Sie logisch denken, ist der Sinn einer privaten Methode: Die öffentliche Hauptmethode wird zu umfangreich oder unübersichtlich. Um dies zu lösen, überarbeiten Sie die Struktur mit Bedacht und erstellen kleine Codestücke, die es verdienen, separate private Methoden zu sein, wodurch Ihre öffentliche Hauptmethode weniger umfangreich wird. Sie überarbeiten dies, indem Sie berücksichtigen, dass diese private Methode später möglicherweise wiederverwendet wird. Es kann Fälle geben, in denen es keine andere öffentliche Methode gibt, die von dieser privaten Methode abhängt, aber die Zukunft kennt.
Betrachtet man den Fall, in dem eine private Methode von vielen anderen öffentlichen Methoden wiederverwendet wird.
Also, wenn ich Ansatz 1 gewählt hätte: Ich hätte Unit-Tests dupliziert und sie wären kompliziert gewesen, da Sie für jeden Zweig der öffentlichen Methode sowie der privaten Methode eine Anzahl von Unit-Tests hatten.
Wenn ich Ansatz 2 gewählt hätte: Der für Unit-Tests geschriebene Code wäre relativ kürzer und viel einfacher zu testen.
Betrachtet man den Fall, dass die private Methode nicht wiederverwendet wird, ist es nicht sinnvoll, einen separaten Komponententest für diese Methode zu schreiben.
Was als Integrationstests betroffen sind, neigen sie erschöpfend und von hohem Niveau zu sein. Sie werden Ihnen mitteilen, dass alle Ihre Klassen zu diesem endgültigen Ergebnis kommen sollten. Weitere Informationen zur Nützlichkeit von Integrationstests finden Sie unter dem angegebenen Link.
quelle