Also habe ich heute mit meinem Teamkollegen über Unit-Tests gesprochen. Das Ganze begann, als er mich fragte: "Hey, wo sind die Tests für diese Klasse, ich sehe nur eine?". Die ganze Klasse war ein Manager (oder ein Service, wenn Sie es so nennen möchten) und fast alle Methoden delegierten einfach Dinge an ein DAO, sodass es ungefähr so aussah:
SomeClass getSomething(parameters) {
return myDao.findSomethingBySomething(parameters);
}
Eine Art Boilerplate ohne Logik (oder zumindest halte ich eine so einfache Delegation nicht für Logik), aber in den meisten Fällen eine nützliche Boilerplate (Schichtentrennung usw.). Und wir hatten eine ziemlich lange Diskussion, ob ich es testen sollte oder nicht (ich denke, es ist erwähnenswert, dass ich das DAO vollständig getestet habe). Seine Hauptargumente sind, dass es nicht (offensichtlich) TDD war und dass jemand den Test sehen möchte, um zu überprüfen, was diese Methode tut (ich weiß nicht, wie es offensichtlicher sein könnte) oder dass in Zukunft jemand das ändern möchte Implementierung und Hinzufügen neuer (oder ähnlicher) Logik (in diesem Fall sollte jemand diese Logik einfach testen ).
Das brachte mich jedoch zum Nachdenken. Sollten wir uns um die höchste Testabdeckung bemühen? Oder ist es nur Kunst um der Kunst willen? Ich sehe einfach keinen Grund für das Testen von Dingen wie:
- Getter und Setter (es sei denn, sie haben tatsächlich eine Logik in sich)
- Code "Boilerplate"
Natürlich würde ein Test für eine solche Methode (mit Mocks) weniger als eine Minute dauern, aber ich denke, das ist immer noch Zeitverschwendung und eine Millisekunde länger für jedes CI.
Gibt es vernünftige / nicht "brennbare" Gründe, warum man jede einzelne (oder so viele wie möglich) Codezeile testen sollte?
quelle
Antworten:
Ich halte mich an Kent Becks Faustregel:
Testen Sie alles, was brechen könnte.
Das ist natürlich einigermaßen subjektiv. Für mich sind Trivial Getters / Setter und One-Liner wie Ihres normalerweise nicht wert. Andererseits verbringe ich die meiste Zeit damit, Komponententests für Legacy-Code zu schreiben, und träume nur von einem schönen TDD-Projekt auf der grünen Wiese ... Bei solchen Projekten gelten andere Regeln. Das Hauptziel des Legacy-Codes ist es, mit möglichst geringem Aufwand so viel wie möglich zu bearbeiten. Daher sind Komponententests in der Regel komplexer und komplexer als Integrationstests, wenn die Terminologie umständlich ist. Und wenn Sie Schwierigkeiten haben, die allgemeine Codeabdeckung von 0% zu steigern, oder es nur geschafft haben, sie auf über 25% zu steigern, sind Unit-Testing-Tests die geringste Ihrer Sorgen.
OTOH In einem TDD-Projekt auf der grünen Wiese ist es möglicherweise sachlicher, Tests auch für solche Methoden zu schreiben. Zumal Sie den Test bereits geschrieben haben, bevor Sie die Chance bekommen, sich zu fragen: "Ist diese eine Zeile einen speziellen Test wert?". Und zumindest sind diese Tests einfach zu schreiben und schnell auszuführen, so dass es auch keine große Sache ist.
quelle
Es gibt einige Arten von Unit-Tests:
Wenn Sie Ihren Test zuerst schreiben würden, wäre dies sinnvoller, da Sie erwarten würden, dass Sie eine Datenzugriffsschicht aufrufen. Test würde zunächst fehlschlagen. Sie würden dann Produktionscode schreiben, um den Test zu bestehen.
Idealerweise sollten Sie logischen Code testen, aber Interaktionen (Objekte, die andere Objekte aufrufen) sind ebenso wichtig. In Ihrem Fall würde ich
Derzeit gibt es dort keine Logik, aber das wird nicht immer der Fall sein.
Wenn Sie jedoch sicher sind, dass diese Methode keine Logik enthält und wahrscheinlich dieselbe bleibt, würde ich in Betracht ziehen, die Datenzugriffsebene direkt vom Verbraucher aufzurufen. Ich würde das nur tun, wenn der Rest des Teams auf der gleichen Seite ist. Sie möchten keine falsche Nachricht an das Team senden, indem Sie sagen: "Hey Leute, es ist in Ordnung, die Domänenschicht zu ignorieren, rufen Sie einfach die Datenzugriffsschicht direkt an."
Ich würde mich auch darauf konzentrieren, andere Komponenten zu testen, wenn es einen Integrationstest für diese Methode gäbe. Ich bin jedoch noch nicht bei einem Unternehmen mit soliden Integrationstests.
Nach alledem würde ich nicht alles blind testen. Ich würde die Hot Spots feststellen (Bauteile mit hoher Komplexität und hoher Bruchgefahr). Ich würde mich dann auf diese Komponenten konzentrieren. Es hat keinen Sinn, eine Codebasis zu haben, bei der 90% der Codebasis recht einfach ist und durch Komponententests abgedeckt werden, während die verbleibenden 10% die Kernlogik des Systems darstellen und aufgrund ihrer Komplexität nicht durch Komponententests abgedeckt werden.
Was ist der Vorteil des Testens dieser Methode? Was sind die Auswirkungen, wenn dies nicht funktioniert? Sind sie katastrophal? Bemühen Sie sich nicht um eine hohe Codeabdeckung. Die Codeabdeckung sollte ein Nebenprodukt einer guten Reihe von Komponententests sein. Beispielsweise können Sie einen Test schreiben, der den Baum abläuft und Ihnen eine 100% ige Abdeckung dieser Methode gibt, oder Sie können drei Einheitentests schreiben, die Ihnen auch eine 100% ige Abdeckung geben. Der Unterschied ist, dass Sie durch das Schreiben von drei Tests Kantenfälle testen, anstatt nur über den Baum zu laufen.
quelle
Hier ist eine gute Möglichkeit, über die Qualität Ihrer Software nachzudenken:
Für Boilerplate- und Trivialfunktionen können Sie sich darauf verlassen, dass die Typprüfung ihre Aufgabe erfüllt, und für den Rest benötigen Sie Testfälle.
quelle
Meiner Meinung nach ist die zyklomatische Komplexität ein Parameter. Wenn eine Methode nicht komplex genug ist (wie Getter und Setter). Es ist kein Komponententest erforderlich. McCabes zyklomatischer Komplexitätsgrad sollte mehr als 1 betragen. Ein anderes Wort sollte mindestens 1 Blockanweisung enthalten.
quelle
Ein klares JA mit TDD (und mit wenigen Ausnahmen)
In Ordnung umstritten, aber ich würde argumentieren, dass jedem, der diese Frage mit "Nein" beantwortet, ein grundlegendes Konzept von TDD fehlt.
Für mich ist die Antwort ein klares Ja, wenn Sie TDD folgen. Wenn nicht, dann ist nein eine plausible Antwort.
Die DDD in TDD
TDD wird oft als Hauptvorteil von dir angeführt.
Trennung von Verantwortung und Umsetzung
Als Programmierer ist es furchtbar verlockend, Attribute als etwas Bedeutungsvolles und Stärkeres und Setteres als eine Art Overhead zu betrachten.
Attribute sind jedoch ein Implementierungsdetail, während Setter und Getter die vertragliche Schnittstelle sind, über die Programme tatsächlich funktionieren.
Es ist viel wichtiger zu buchstabieren, dass ein Objekt:
und
dann, wie dieser Zustand tatsächlich gespeichert wird (für die ein Attribut die häufigste, aber nicht die einzige Möglichkeit ist).
Ein Test wie
ist wichtig für den Dokumentationsteil von TDD.
Die Tatsache, dass die eventuelle Implementierung trivial ist (Attribut) und keinen Verteidigungsvorteil mit sich bringt , sollte Ihnen beim Schreiben des Tests nicht bekannt sein.
Das Fehlen von Round-Trip-Engineering ...
Eines der Hauptprobleme in der Welt der Systementwicklung ist das Fehlen von Round-Trip-Engineering 1 - der Entwicklungsprozess eines Systems ist in disjunkte Unterprozesse fragmentiert, deren Artefakte (Dokumentation, Code) häufig inkonsistent sind.
1 Brodie, Michael L. "John Mylopoulos: Samen der konzeptuellen Modellierung nähen." Konzeptuelle Modellierung: Grundlagen und Anwendungen. Springer Berlin Heidelberg, 2009. 1-9.
... und wie TDD das löst
Es ist der Dokumentationsteil von TDD, der sicherstellt, dass die Spezifikationen des Systems und sein Code immer konsistent sind.
Zuerst entwerfen, später implementieren
Innerhalb von TDD schreiben wir zuerst einen Test, bei dem die Abnahme fehlgeschlagen ist, und schreiben dann den Code, der sie passieren lässt.
Innerhalb des übergeordneten BDD schreiben wir zuerst Szenarien und lassen sie dann passieren.
Warum sollten Sie Setter und Getter ausschließen?
Theoretisch ist es innerhalb von TDD durchaus möglich, dass eine Person den Test schreibt und eine andere Person den Code implementiert, der ihn bestehen lässt.
Also frag dich:
Da Getter und Setter eine öffentliche Schnittstelle zu einer Klasse sind, lautet die Antwort offensichtlich Ja , oder es gibt keine Möglichkeit, den Status eines Objekts festzulegen oder abzufragen.
Wenn Sie den Code zuerst schreiben, ist die Antwort möglicherweise nicht so eindeutig.
Ausnahmen
Es gibt einige offensichtliche Ausnahmen von dieser Regel - Funktionen, die klare Implementierungsdetails enthalten und eindeutig nicht Teil des Systemdesigns sind.
Zum Beispiel die lokale Methode 'B ()':
Oder die private Funktion
square()
hier:Oder eine andere Funktion, die nicht Teil einer
public
Schnittstelle ist, die im Entwurf der Systemkomponente geschrieben werden muss.quelle
Wenn Sie mit einer philosophischen Frage konfrontiert werden, kehren Sie zu den fahrerischen Anforderungen zurück.
Ist es Ihr Ziel, einigermaßen zuverlässige Software zu wettbewerbsfähigen Kosten herzustellen?
Oder ist es so, dass Software fast unabhängig von den Kosten mit der höchstmöglichen Zuverlässigkeit hergestellt wird?
Bis zu einem gewissen Punkt stimmen die beiden Ziele Qualität und Entwicklungsgeschwindigkeit / -kosten überein: Sie verbringen weniger Zeit mit dem Schreiben von Tests als mit dem Beheben von Fehlern.
Aber darüber hinaus tun sie es nicht. Es ist nicht so schwer, einen gemeldeten Fehler pro Entwickler und Monat zu finden. Wenn Sie das auf einen von zwei Monaten halbieren, wird nur ein Budget von vielleicht ein oder zwei Tagen freigesetzt, und so viele zusätzliche Tests werden Ihre Fehlerquote wahrscheinlich nicht halbieren. Es ist also kein einfacher Gewinn mehr. Sie müssen dies anhand der Fehlerkosten für den Kunden begründen.
Diese Kosten variieren (und, wenn Sie böse sein möchten, auch ihre Fähigkeit, diese Kosten auf dem Markt oder in einem Rechtsstreit für Sie geltend zu machen). Du willst nicht böse sein, also zählst du diese Kosten vollständig zurück; manchmal machen einige Tests die Welt durch ihre Existenz immer noch global ärmer.
Kurz gesagt, wenn Sie versuchen, die gleichen Standards blind auf eine interne Website wie Passagierflugzeug-Flugsoftware anzuwenden, werden Sie entweder arbeitslos oder im Gefängnis sitzen.
quelle
Ihre Antwort darauf hängt von Ihrer Philosophie ab (glauben Sie, dass es Chicago gegen London ist? Ich bin sicher, dass jemand es nachschlagen wird). Die Jury ist immer noch nicht über den zeiteffektivsten Ansatz informiert (schließlich ist dies der größte Treiber dieser Zeit, die weniger für Korrekturen aufgewendet wurde).
Einige Ansätze sagen, dass nur die öffentliche Schnittstelle getestet wird, andere sagen, dass die Reihenfolge jedes Funktionsaufrufs in jeder Funktion getestet wird. Viele heilige Kriege wurden geführt. Mein Rat ist, beide Ansätze auszuprobieren. Wählen Sie eine Codeeinheit und machen Sie es wie X und eine andere wie Y. Gehen Sie nach ein paar Monaten Test und Integration zurück und sehen Sie, welche besser zu Ihren Anforderungen passt.
quelle
Das ist eine knifflige Frage.
Genau genommen würde ich sagen, dass es nicht notwendig ist. Es ist besser, Tests auf BDD-Einheits- und Systemebene zu schreiben, die sicherstellen, dass die Geschäftsanforderungen in positiven und negativen Szenarien wie beabsichtigt funktionieren.
Das heißt, wenn Ihre Methode von diesen Testfällen nicht abgedeckt wird, müssen Sie sich fragen, warum sie überhaupt existiert und ob sie benötigt wird oder ob versteckte Anforderungen im Code vorhanden sind, die sich nicht in Ihrer Dokumentation oder in User Stories widerspiegeln sollte in einem BDD-Testfall codiert werden.
Persönlich möchte ich die Zeilenabdeckung bei etwa 85-95% halten und das Einchecken in die Hauptzeile, um sicherzustellen, dass die vorhandene Testabdeckung pro Zeile für alle Codedateien auf diesem Niveau liegt und keine Dateien aufgedeckt werden.
Unter der Annahme, dass die besten Testmethoden befolgt werden, ergibt sich eine ausreichende Abdeckung, ohne dass Entwickler Zeit verlieren müssen, um herauszufinden, wie eine zusätzliche Abdeckung für schwer zu übenden Code oder Trivialcode einfach aus Gründen der Abdeckung erzielt werden kann.
quelle
Das Problem ist die Frage selbst. Sie müssen nicht alle "Methoden" oder "Klassen" testen, um alle Funktionen Ihres Systems zu testen.
Es ist das zentrale Denken in Bezug auf Merkmale / Verhalten, anstatt in Bezug auf Methoden und Klassen zu denken. Natürlich gibt es hier eine Methode, die Unterstützung für eine oder mehrere Funktionen bietet. Am Ende wird der gesamte Code getestet, zumindest der gesamte Code in Ihrer Codebasis.
In Ihrem Szenario ist diese "Manager" -Klasse möglicherweise überflüssig oder unnötig (wie alle Klassen mit einem Namen, der das Wort "Manager" enthält), oder möglicherweise nicht, scheint jedoch ein Implementierungsdetail zu sein. Wahrscheinlich verdient diese Klasse keine Einheit Test, weil diese Klasse keine relevante Geschäftslogik hat. Möglicherweise benötigen Sie diese Klasse, damit ein Feature funktioniert. Der Test für dieses Feature deckt diese Klasse ab. Auf diese Weise können Sie diese Klasse umgestalten und Tests durchführen lassen, die sicherstellen, dass das, was zählt, Ihre Features auch nach dem Umgestalten noch funktionieren.
Denken Sie an Features / Verhaltensweisen, die nicht in Methodenklassen vorkommen. Ich kann dies nicht oft genug wiederholen.
quelle
Ja, im Idealfall 100%, aber einige Dinge sind nicht prüfbar.
Getter / Setter sind dumm - benutze sie einfach nicht. Stellen Sie stattdessen Ihre Mitgliedsvariable in den öffentlichen Bereich.
Holen Sie sich gemeinsamen Code heraus und testen Sie ihn. Das sollte so einfach sein.
Andernfalls könnten Sie einige sehr offensichtliche Fehler übersehen. Unit-Tests sind wie ein sicheres Netz, um bestimmte Arten von Fehlern zu erkennen, und Sie sollten es so oft wie möglich verwenden.
Und das Letzte: Ich arbeite an einem Projekt, bei dem die Leute nicht ihre Zeit damit verschwenden wollten, Komponententests für einen "einfachen Code" zu schreiben, sondern später beschlossen, überhaupt nicht zu schreiben. Am Ende verwandelten sich Teile des Codes in eine große Schlammkugel .
quelle