it-swarm.com.de

Sollten wir alle unsere Methoden testen?

Also hatte ich heute ein Gespräch mit meinem Teamkollegen über Unit-Tests. Das Ganze begann, als er mich fragte: "Hey, wo sind die Tests für diese Klasse, ich sehe nur einen?". Die ganze Klasse war ein Manager (oder ein Dienst, wenn Sie es lieber so nennen) und fast alle Methoden delegierten einfach Dinge an ein DAO, also war es ähnlich wie:

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

Eine Art Boilerplate ohne Logik (oder zumindest halte ich eine so einfache Delegierung nicht für logisch), aber in den meisten Fällen eine nützliche Boilerplate (Schichttrennung usw.). Und wir hatten eine ziemlich lange Diskussion darüber, ob ich es einem Unit-Test unterziehen sollte oder nicht (ich denke, es ist erwähnenswert, dass ich das DAO vollständig einem Unit-Test unterzogen habe). Seine Hauptargumente sind, dass es (offensichtlich) kein TDD war und dass jemand den Test sehen möchte, um zu überprüfen, was diese Methode bewirkt (ich weiß nicht, wie es offensichtlicher sein könnte) oder dass in Zukunft jemand das ändern möchte Implementierung und fügen Sie neue (oder eher "beliebige") Logik hinzu (in diesem Fall sollte jemand einfach das testen Logik).

Das brachte mich jedoch zum Nachdenken. Sollten wir die höchste Testabdeckung in% anstreben? Oder ist es dann einfach eine Kunst um der Kunst willen? Ich sehe einfach keinen Grund, Dinge zu testen wie:

  • getter und Setter (es sei denn, sie enthalten tatsächlich eine Logik)
  • 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 rationale/nicht "brennbare" Gründe, warum man jede einzelne (oder so viele wie möglich) Codezeile testen sollte?

63
Zenzen

Ich halte mich an Kent Becks Faustregel:

Testen Sie alles, was möglicherweise kaputt gehen könnte.

Das ist natürlich bis zu einem gewissen Grad subjektiv. Für mich sind triviale Getter/Setter und Einzeiler wie Ihre oben normalerweise nicht wert. Andererseits verbringe ich die meiste Zeit damit, Komponententests für Legacy-Code zu schreiben, und träume nur von einem TDD-Projekt auf der grünen Wiese ... Bei solchen Projekten sind die Regeln unterschiedlich. Bei Legacy-Code besteht das Hauptziel darin, mit möglichst geringem Aufwand so viel Boden wie möglich abzudecken. Daher sind Unit-Tests in der Regel auf höherer Ebene und komplexer, eher wie Integrationstests, wenn man die Terminologie pedantisch betrachtet. Und wenn Sie Schwierigkeiten haben, die gesamte Codeabdeckung von 0% zu erhöhen, oder es nur geschafft haben, sie auf über 25% zu erhöhen, sind Unit-Testing-Getter und -Setter die geringste Sorge.

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 sich fragen können: "Ist diese eine Zeile einen speziellen Test wert?". Und zumindest sind diese Tests trivial zu schreiben und schnell auszuführen, so dass es in beiden Fällen keine große Sache ist.

49
Péter Török

Es gibt nur wenige Arten von Unit-Tests:

  • Staatsbasiert. Sie handeln und behaupten dann gegen den Zustand des Objekts. Z.B. Ich mache eine Einzahlung. Ich überprüfe dann, ob sich das Gleichgewicht erhöht hat.
  • Rückgabewert basierend. Sie handeln und behaupten gegen Rückgabewert.
  • Interaktionsbasiert. Sie überprüfen, ob Ihr Objekt ein anderes Objekt aufgerufen hat. Dies scheint das zu sein, was Sie in Ihrem Beispiel tun.

Wenn Sie Ihren Test zuerst schreiben würden, wäre dies sinnvoller - da Sie erwarten würden, eine Datenzugriffsschicht aufzurufen. 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

  • Überprüfen Sie, ob ich die Datenzugriffsschicht mit dem genauen Parameter aufgerufen habe, der übergeben wurde.
  • Überprüfen Sie, ob es nur einmal aufgerufen wurde.
  • Überprüfen Sie, ob ich genau das zurückgebe, was mir von der Datenzugriffsschicht gegeben wurde. Andernfalls könnte ich genauso gut null zurückgeben.

Derzeit gibt es dort keine Logik, aber dies wird nicht immer der Fall sein.

Wenn Sie jedoch sicher sind, dass diese Methode keine Logik enthält und wahrscheinlich gleich bleibt, würde ich in Betracht ziehen, die Datenzugriffsschicht direkt vom Verbraucher aufzurufen. Ich würde dies nur tun, wenn der Rest des Teams auf derselben 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 auf.".

Ich würde mich auch darauf konzentrieren, andere Komponenten zu testen, wenn es einen Integrationstest für diese Methode gäbe. Ich sehe jedoch noch kein Unternehmen mit soliden Integrationstests.

Nachdem ich das alles gesagt habe, würde ich nicht alles blind testen. Ich würde die Hot Spots etablieren (Komponenten mit hoher Komplexität und hohem Bruchrisiko). Ich würde mich dann auf diese Komponenten konzentrieren. Es macht keinen Sinn, eine Codebasis zu haben, in der 90% der Codebasis recht einfach sind und durch Komponententests abgedeckt werden, wenn die restlichen 10% die Kernlogik des Systems darstellen und sie 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? Streben Sie nicht nach einer hohen Codeabdeckung. Die Codeabdeckung sollte ein Nebenprodukt einer guten Reihe von Komponententests sein. Sie können beispielsweise einen Test schreiben, der den Baum durchläuft und Ihnen eine 100% ige Abdeckung dieser Methode bietet, oder Sie können drei Komponententests schreiben, die Ihnen auch eine 100% ige Abdeckung bieten. Der Unterschied besteht darin, dass Sie durch das Schreiben von drei Tests Edge-Fälle testen, anstatt nur über den Baum zu gehen.

13
CodeART

Hier ist eine gute Möglichkeit, über die Qualität Ihrer Software nachzudenken:

  1. die Typprüfung behandelt einen Teil des Problems.
  2. tests erledigen den Rest

Für Boilerplate- und Trivial-Funktionen können Sie sich bei der Arbeit auf die Typprüfung verlassen, und für den Rest benötigen Sie Testfälle.

9
tp1

Meiner Meinung nach ist die zyklomatische Komplexität ein Parameter. Wenn eine Methode nicht komplex genug ist (wie Getter und Setter). Es sind keine Unit-Tests erforderlich. McCabes zyklomatische Komplexitätsstufe sollte mehr als 1 betragen. Bei einem anderen Wort sollte mindestens 1 Blockanweisung vorhanden sein.

6
Fırat KÜÇÜK

Ein klares JA mit TDD (und mit wenigen Ausnahmen)

Umstritten in Ordnung, 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 Sie nicht sind, dann ist nein eine plausible Antwort.

Die DDD in TDD

TDD wird oft als Hauptvorteil bezeichnet.

  • Verteidigung
    • Sicherstellen, dass Code kann sich ändern aber nicht sein Verhalten.
    • Dies ermöglicht die sehr wichtige Praxis von Refactoring.
    • Sie erhalten diese TDD oder nicht.
  • Design
    • Sie angeben was etwas tun soll, wie es sich verhalten soll vor der Implementierung es.
    • Dies bedeutet oft informierter msetzungsentscheidungen.
  • Dokumentation
    • Die Testsuite sollte als Spezifikation (Anforderungen) Dokumentation dienen.
    • Die Verwendung von Tests für diesen Zweck bedeutet, dass die Dokumentation und Implementierung immer in einem konsistenten Zustand sind - eine Änderung an einem bedeutet eine Änderung an einem anderen. Vergleichen Sie mit dem Beibehalten von Anforderungen und Design in einem separaten Word-Dokument.

Verantwortung von Umsetzung trennen

Als Programmierer ist es furchtbar verlockend, Attribute als etwas von Bedeutung und Getter und Setter 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:

Erlauben Sie seinen Clients, ihren Status zu ändern

und

Ermöglichen Sie seinen Clients, ihren Status abzufragen

dann, wie dieser Zustand tatsächlich gespeichert wird (für die ein Attribut der häufigste, aber nicht der einzige Weg ist).

Ein Test wie

(The Painter class) should store the provided colour

ist wichtig für den Dokumentation Teil von TDD.

Die Tatsache, dass die eventuelle Implementierung trivial ist (Attribut) und keinen Verteidigung Nutzen bringt, sollte Ihnen beim Schreiben des Tests unbekannt sein.

Der Mangel an Round-Trip-Technik ...

Eines der Hauptprobleme in der Welt der Systementwicklung ist das Fehlen von Round-Trip-Engineering 1 - Der Entwicklungsprozess eines Systems ist in nicht zusammenhängende Unterprozesse fragmentiert, deren Artefakte (Dokumentation, Code) häufig inkonsistent sind.

1Brodie, Michael L. "John Mylopoulos: Samen der konzeptuellen Modellierung nähen." Konzeptionelle Modellierung: Grundlagen und Anwendungen. Springer Berlin Heidelberg, 2009. 1-9.

... und wie TDD es löst

Es ist der Dokumentation Teil von TDD, der sicherstellt, dass die Spezifikationen des Systems und seines Codes immer konsistent sind.

Zuerst entwerfen, später implementieren

Innerhalb von TDD schreiben wir zuerst einen fehlgeschlagenen Abnahmetest und erst dann den Code, der sie passieren lässt.

Innerhalb des übergeordneten BDD schreiben wir zuerst Szenarien und lassen sie dann bestehen.

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 bestanden hat.

Fragen Sie sich also:

Sollte die Person, die die Tests für eine Klasse schreibt, Getter und Setter erwähnen.

Da Getter und Setter eine öffentliche Schnittstelle zu einer Klasse sind, lautet die Antwort offensichtlich yes, 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 eindeutige Implementierungsdetails darstellen und eindeutig nicht Teil des Systemdesigns sind.

Zum Beispiel die lokale Methode 'B ()':

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

Oder die private Funktion square() hier:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

Oder eine andere Funktion, die nicht Teil einer public -Schnittstelle ist, die beim Entwurf der Systemkomponente buchstabiert werden muss.

3
Izhaki

Wenn Sie mit einer philosophischen Frage konfrontiert werden, kehren Sie zu den Fahranforderungen zurück.

Ist es Ihr Ziel, einigermaßen zuverlässige Software zu wettbewerbsfähigen Kosten zu produzieren?

Oder geht es darum, Software mit höchstmöglicher Zuverlässigkeit nahezu unabhängig von den Kosten herzustellen?

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, beispielsweise einen gemeldeten Fehler pro Entwickler und Monat zu erreichen. Wenn Sie dies auf eins pro zwei Monate halbieren, wird nur ein Budget von vielleicht ein oder zwei Tagen freigesetzt, und so viele zusätzliche Tests werden Ihre Fehlerrate wahrscheinlich nicht halbieren. Es ist also kein einfacher Gewinn/Gewinn mehr. Sie müssen dies anhand der Defektkosten für den Kunden begründen.

Diese Kosten variieren (und, wenn Sie böse sein wollen, auch ihre Fähigkeit, diese Kosten für Sie durchzusetzen, sei es durch den Markt oder durch eine Klage). 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 ärmer.

Kurz gesagt, wenn Sie versuchen, blindlings dieselben Standards auf eine interne Website anzuwenden wie die Flugsoftware für Passagierflugzeuge, werden Sie entweder aus dem Geschäft oder im Gefängnis entlassen.

1
soru

Es ist eine schwierige Frage.

Genau genommen würde ich sagen, dass es nicht notwendig ist. Sie sind besser dran, Tests auf Einheiten- und Systemebene im BDD-Stil 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 der Code versteckte Anforderungen enthält, die sich nicht in Ihrer Dokumentation oder in Ihren User Stories widerspiegeln sollte in einem Testfall im BDD-Stil codiert werden.

Persönlich mag ich es, die Abdeckung pro Zeile bei etwa 85-95% zu halten und das Einchecken in die Hauptleitung zu gewährleisten, um sicherzustellen, dass die vorhandene Abdeckung durch Unit-Tests pro Zeile diese Stufe für alle Codedateien erreicht und keine Dateien aufgedeckt werden.

Unter der Annahme, dass die besten Testmethoden befolgt werden, bietet dies eine ausreichende Abdeckung, ohne dass Entwickler Zeit damit verschwenden müssen, herauszufinden, wie zusätzliche Abdeckung für schwer zu übenden Code oder trivialen Code nur aus Gründen der Abdeckung erzielt werden kann.

0
Keith Brings

Ihre Antwort darauf hängt von Ihrer Philosophie ab (glauben Sie, es ist Chicago gegen London? Ich bin sicher, jemand wird es nachschlagen). Die Jury ist immer noch nicht über den zeiteffektivsten Ansatz informiert (denn schließlich ist dies der größte Treiber für diese weniger Zeit, die 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. Es wurden viele heilige Kriege geführt. Mein Rat ist, beide Ansätze auszuprobieren. Wählen Sie eine Codeeinheit aus und machen Sie es wie X und eine wie Y. Nach ein paar Monaten Test und Integration gehen Sie zurück und sehen Sie, welche besser zu Ihren Anforderungen passt.

0
anon