it-swarm.com.de

Wie erleichtern Unit-Tests das Design?

Unser Kollege fördert das Schreiben von Komponententests, um uns tatsächlich dabei zu helfen, unser Design zu verfeinern und Dinge umzugestalten, aber ich verstehe nicht, wie. Wenn ich eine CSV-Datei lade und analysiere, wie hilft mir der Komponententest (Validieren der Werte in den Feldern), mein Design zu überprüfen? Er erwähnte Kopplung und Modularität usw., aber für mich macht es nicht viel Sinn - aber ich habe nicht viel theoretischen Hintergrund.

Das ist nicht dasselbe wie die Frage, die Sie als Duplikat markiert haben. Ich würde mich für konkrete Beispiele interessieren, wie dies hilft, und nicht nur für die Theorie, dass "es hilft". Ich mag die Antwort unten und den Kommentar, aber ich würde gerne mehr erfahren.

43
User039402

Unit-Tests erleichtern nicht nur das Design, sondern dies ist auch einer ihrer Hauptvorteile.

Das Schreiben von Test-First führt zu Modularität und sauberer Codestruktur.

Wenn Sie Ihren Code test-first schreiben, werden Sie feststellen, dass alle "Bedingungen" einer bestimmten Codeeinheit natürlich in Abhängigkeiten (normalerweise über Mocks oder Stubs) verschoben werden, wenn Sie sie in Ihrem Code annehmen.

"Wenn die Bedingung x gegeben ist, erwarte das Verhalten y" wird oft zu einem Stub, der x liefert (dies ist ein Szenario , in dem der Test durchgeführt wird muss das Verhalten der aktuellen Komponente überprüfen) und y wird zu einem Mock, ein Aufruf, der am Ende des Tests überprüft wird (es sei denn, es ist ein "sollte y zurückgeben"). In diesem Fall überprüft der Test nur den Rückgabewert explizit.

Sobald sich diese Einheit wie angegeben verhält, schreiben Sie die Abhängigkeiten (für x und y), die Sie entdeckt haben.

Dies macht das Schreiben von sauberem, modularem Code zu einem sehr einfachen und natürlichen Prozess, bei dem es ansonsten oft einfach ist, Verantwortlichkeiten zu verwischen und Verhaltensweisen miteinander zu verbinden, ohne es zu merken.

Wenn Sie später Tests schreiben, erfahren Sie, wenn Ihr Code schlecht strukturiert ist.

Wenn das Schreiben von Tests für einen Code schwierig wird, weil zu viele Dinge zu stummeln oder zu verspotten sind oder weil die Dinge zu eng miteinander verbunden sind, wissen Sie, dass Sie Verbesserungen an Ihrem Code vornehmen müssen.

Wenn das "Ändern von Tests" zu einer Belastung wird, weil es so viele Verhaltensweisen in einer Einheit gibt, wissen Sie, dass Sie Verbesserungen an Ihrem Code vornehmen müssen (oder einfach an Ihrem Ansatz, Tests zu schreiben - aber dies ist meiner Erfahrung nach normalerweise nicht der Fall). .

Wenn Ihre Szenarien zu kompliziert werden ("wenn x und y und z dann ..."), weil Sie mehr abstrahieren müssen, wissen Sie, dass Sie Verbesserungen vornehmen müssen dein Code.

Wenn Sie aufgrund von Duplizierung und Redundanz dieselben Tests in zwei verschiedenen Fixtures durchführen, wissen Sie, dass Sie Verbesserungen an Ihrem Code vornehmen müssen.

Hier ist ein ausgezeichneter Vortrag von Michael Feathers zeigt die sehr enge Beziehung zwischen Testbarkeit und Design im Code (ursprünglich gepostet von displayName in den Kommentaren). Der Vortrag befasst sich auch mit einigen häufigen Beschwerden und Missverständnissen über gutes Design und Testbarkeit im Allgemeinen.

3
Ant P

Das Tolle an Unit-Tests ist, dass Sie Ihren Code so verwenden können, wie andere Programmierer Ihren Code verwenden.

Wenn Ihr Code für den Komponententest umständlich ist, ist die Verwendung wahrscheinlich umständlich. Wenn Sie keine Abhängigkeiten einfügen können, ohne durch die Rahmen zu springen, ist die Verwendung Ihres Codes wahrscheinlich unflexibel. Und wenn Sie viel Zeit damit verbringen müssen, Daten einzurichten oder herauszufinden, in welcher Reihenfolge die Dinge zu tun sind, hat Ihr zu testender Code wahrscheinlich zu viel Kopplung und wird ein Problem für die Arbeit sein.

103
Telastyn

Es hat eine ganze Weile gedauert, bis mir klar wurde, aber der wahre Vorteil (für mich kann Ihre Laufleistung variieren) bei der Durchführung einer testgetriebenen Entwicklung (mit Unit-Tests) ist, dass Sie müssen Mach das API-Design im Voraus!

Ein typischer Ansatz für die Entwicklung besteht darin, zunächst herauszufinden, wie ein bestimmtes Problem gelöst werden kann, und mit diesem Wissen und dem anfänglichen Implementierungsdesign eine Möglichkeit zu finden, Ihre Lösung aufzurufen. Dies kann zu interessanten Ergebnissen führen.

Wenn Sie TDD ausführen, müssen Sie als erstes den Code schreiben, der Ihre Lösung verwendet. Eingabeparameter und erwartete Ausgabe, damit Sie sicherstellen können, dass sie richtig ist. Dazu müssen Sie herausfinden, was Sie tatsächlich tun müssen, damit Sie aussagekräftige Tests erstellen können. Dann und nur dann implementieren Sie die Lösung. Ich habe auch die Erfahrung gemacht, dass es klarer wird, wenn Sie genau wissen, was Ihr Code erreichen soll.

Nach der Implementierung können Sie mithilfe von Komponententests sicherstellen, dass das Refactoring die Funktionalität nicht beeinträchtigt, und eine Dokumentation zur Verwendung Ihres Codes bereitstellen (von der Sie wissen, dass sie beim Bestehen des Tests richtig ist!). Diese sind jedoch zweitrangig - der größte Vorteil ist die Denkweise bei der Erstellung des Codes.

Ich würde zu 100% zustimmen, dass Unit-Tests "uns helfen, unser Design zu verfeinern und Dinge zu überarbeiten".

Ich bin mir nicht sicher, ob sie Ihnen bei der Ausführung des anfängliches Design helfen. Ja, sie weisen offensichtliche Mängel auf und zwingen Sie, darüber nachzudenken, wie ich den Code testbar machen kann. Dies sollte zu weniger Nebenwirkungen, einer einfacheren Konfiguration und Einrichtung usw. führen.

Nach meiner Erfahrung führen jedoch zu vereinfachte Komponententests, die geschrieben wurden, bevor Sie wirklich verstanden haben, wie das Design aussehen soll (das ist zwar eine Übertreibung von Hardcore-TDD, aber zu oft schreiben Codierer einen Test, bevor sie viel nachdenken), häufig zu Anämien Domänenmodelle, die zu viele Interna verfügbar machen.

Meine Erfahrung mit TDD war vor einigen Jahren, daher bin ich daran interessiert zu hören, welche neueren Techniken beim Schreiben von Tests helfen können, die das zugrunde liegende Design nicht zu sehr beeinflussen. Vielen Dank.

6
user949300

Mit Unit-Tests können Sie sehen, wie die Schnittstellen zwischen Funktionen funktionieren, und häufig erhalten Sie Einblicke, wie Sie sowohl das lokale Design als auch das Gesamtdesign verbessern können. Wenn Sie Ihre Komponententests während der Entwicklung Ihres Codes entwickeln, verfügen Sie außerdem über eine vorgefertigte Regressionstestsuite. Es spielt keine Rolle, ob Sie eine Benutzeroberfläche oder eine Backend-Bibliothek entwickeln.

Sobald das Programm entwickelt wurde (mit Komponententests), können Sie, wenn Fehler aufgedeckt werden, Tests hinzufügen, um zu bestätigen, dass die Fehler behoben sind.

Ich benutze TDD für einige meiner Projekte. Ich habe mich sehr bemüht, Beispiele zu erstellen, die ich aus Lehrbüchern oder Papieren ziehe, die als korrekt gelten, und den Code, den ich entwickle, anhand dieses Beispiels zu testen. Alle Missverständnisse, die ich bezüglich der Methoden habe, werden sehr offensichtlich.

Ich neige dazu, etwas lockerer zu sein als einige meiner Kollegen, da es mir egal ist, ob der Code zuerst geschrieben wird oder der Test zuerst geschrieben wird.

5
Robert Baron

Wenn Sie Ihren Parser auf Einheitentest testen möchten, um die ordnungsgemäße Abgrenzung von Werten zu erkennen, möchten Sie ihn möglicherweise eine Zeile aus einer CSV-Datei übergeben. Um Ihren Test direkt und kurz zu gestalten, möchten Sie ihn möglicherweise mit einer Methode testen, die eine Zeile akzeptiert.

Dadurch trennen Sie automatisch das Lesen von Zeilen vom Lesen einzelner Werte.

Auf einer anderen Ebene möchten Sie möglicherweise nicht alle Arten von physischen CSV-Dateien in Ihr Testprojekt einfügen, sondern etwas Lesbareres tun, indem Sie einfach eine große CSV-Zeichenfolge in Ihrem Test deklarieren, um die Lesbarkeit und die Absicht des Tests zu verbessern. Dies führt dazu, dass Sie Ihren Parser von allen E/A-Vorgängen entkoppeln, die Sie an anderer Stelle ausführen würden.

Nur ein einfaches Beispiel, fang einfach an, es zu üben, du wirst irgendwann die Magie spüren (ich habe).

5
Joppe

Einfach ausgedrückt hilft das Schreiben von Komponententests dabei, Fehler in Ihrem Code aufzudecken.

Diese spektakuläre Anleitung zum Schreiben von testbarem Code , geschrieben von Jonathan Wolter, Russ Ruffer und Miško Hevery, enthält zahlreiche Beispiele dafür, wie Codefehler, die das Testen behindern, auch eine einfache Wiederverwendung und Flexibilität des Codes verhindern gleicher Code. Wenn Ihr Code testbar ist, ist er daher einfacher zu verwenden. Die meisten "Moralvorstellungen" sind lächerlich einfache Tipps, die das Code-Design erheblich verbessern ( Dependency Injection FTW).

Beispiel: Es ist sehr schwierig zu testen, ob die Methode computeStuff ordnungsgemäß funktioniert, wenn der Cache mit dem Entfernen von Daten beginnt. Dies liegt daran, dass Sie dem Cache manuell Mist hinzufügen müssen, bis der "bigCache" fast voll ist.

public OopsIHardcoded {

   Cache cacheOfExpensiveComputations;

   OopsIHardcoded() {
       this.cacheOfExpensiveComputation = buildBigCache();
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Wenn wir jedoch die Abhängigkeitsinjektion verwenden, ist es viel einfacher zu testen, ob die Methode computeStuff ordnungsgemäß funktioniert, wenn der Cache mit dem Entfernen von Daten beginnt. Alles, was wir tun, ist einen Test zu erstellen, in dem wir new HereIUseDI(buildSmallCache()); aufrufen. Beachten Sie, dass wir eine differenziertere Kontrolle über das Objekt haben und es sich sofort auszahlt.

public HereIUseDI {

   Cache cacheOfExpensiveComputations;

   HereIUseDI(Cache cache) {
       this.cacheOfExpensiveComputation = cache;
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Ähnliche Vorteile ergeben sich, wenn unser Code Daten erfordert, die normalerweise in einer Datenbank gespeichert sind. Geben Sie einfach genau die Daten ein, die Sie benötigen.

4
Ivan

Je nachdem, was unter "Unit-Tests" zu verstehen ist, denke ich nicht, dass Tests auf niedriger Ebene Unit ein gutes Design ermöglichen, ebenso wenig wie ein etwas höheres Level Integration Tests - Tests, die testen, ob eine Gruppe von Akteuren (Klassen, Funktionen, was auch immer) in Ihrem Code ordnungsgemäß kombiniert wird, um eine ganze Reihe wünschenswerter Verhaltensweisen zu erzeugen, die zwischen dem Entwicklungsteam und dem Product Owner vereinbart wurden.

Wenn Sie Tests auf diesen Ebenen schreiben können, werden Sie dazu gebracht, netten, logischen, API-ähnlichen Code zu erstellen, der nicht viele verrückte Abhängigkeiten erfordert. Der Wunsch nach einem einfachen Test-Setup wird Sie natürlich dazu bringen, nicht viele zu haben verrückte Abhängigkeiten oder eng gekoppelter Code.

Machen Sie aber keinen Fehler - nit-Tests können sowohl zu schlechtem als auch zu gutem Design führen. Ich habe gesehen, dass Entwickler ein Stück Code, das bereits ein schönes logisches Design und ein einziges Problem hat, auseinander genommen und mehr Schnittstellen nur zum Testen eingeführt haben, wodurch der Code weniger lesbar und schwerer zu ändern ist und möglicherweise sogar mehr Fehler, wenn der Entwickler entschieden hat, dass viele Unit-Tests auf niedriger Ebene bedeuten, dass keine Tests auf höherer Ebene erforderlich sind. Ein besonderes Lieblingsbeispiel ist ein Fehler, den ich behoben habe, bei dem es viele sehr kaputte, testbare Codes gab, die sich darauf beziehen, Informationen in die Zwischenablage und aus der Zwischenablage zu entfernen. Alles aufgeschlüsselt und auf sehr kleine Detailebenen entkoppelt, mit vielen Schnittstellen, vielen Verspottungen in den Tests und anderen lustigen Dingen. Nur ein Problem: Es gab keinen Code, der tatsächlich mit dem Zwischenablage-Mechanismus des Betriebssystems interagierte, sodass der Code in der Produktion tatsächlich nichts tat.

nit-Tests können definitiv Ihr Design vorantreiben - aber sie führen Sie nicht automatisch zu einem guten Design. Sie müssen Ideen darüber haben, was gutes Design ist, das über das reine Testen dieses Codes hinausgeht es ist testbar, deshalb ist es gut '.

Wenn Sie zu den Personen gehören, für die "Komponententests" "automatisierte Tests, die nicht über die Benutzeroberfläche ausgeführt werden" bedeuten, sind einige dieser Warnungen möglicherweise nicht so relevant - wie gesagt, ich denke, diese sind höher Integrationstests auf Ebene sind oft die nützlicheren, wenn es darum geht, Ihr Design voranzutreiben.