Reicht es aus, Akzeptanz- und Integrationstests anstelle von Komponententests zu verwenden?

62

Kurze Einführung in diese Frage. Ich benutze jetzt TDD und in letzter Zeit BDD seit über einem Jahr. Ich benutze Techniken wie das Verspotten, um meine Tests effizienter zu schreiben. In letzter Zeit habe ich ein persönliches Projekt gestartet, um ein kleines Money-Management-Programm für mich zu schreiben. Da ich keinen alten Code hatte, war es das perfekte Projekt, um mit TDD zu beginnen. Leider habe ich die Freude an TDD nicht so sehr erlebt. Es hat sogar meinen Spaß so sehr verdorben, dass ich das Projekt aufgegeben habe.

Was war das Problem? Nun, ich habe den TDD-ähnlichen Ansatz verwendet, um die Tests / Anforderungen das Design des Programms weiterentwickeln zu lassen. Das Problem war, dass mehr als die Hälfte der Entwicklungszeit für das Schreiben / Refactor-Tests. Am Ende wollte ich keine Features mehr implementieren, da ich viele Tests überarbeiten und schreiben musste.

Bei der Arbeit habe ich viel Legacy-Code. Hier schreibe ich immer mehr Integrations- und Akzeptanztests und weniger Unit-Tests. Dies scheint kein schlechter Ansatz zu sein, da Fehler meist durch Akzeptanz- und Integrationstests erkannt werden.

Meine Idee war, dass ich am Ende mehr Integrations- und Akzeptanztests schreiben könnte als Unit-Tests. Wie ich bereits sagte, sind die Komponententests zur Erkennung von Fehlern nicht besser als die Integrations- / Abnahmetests. Unit-Test sind auch gut für das Design. Da ich viele von ihnen geschrieben habe, sind meine Klassen immer so gestaltet, dass sie gut testbar sind. Darüber hinaus führt der Ansatz, dass die Tests / Anforderungen das Design leiten, in den meisten Fällen zu einem besseren Design. Der letzte Vorteil von Unit-Tests ist, dass sie schneller sind. Ich habe genug Integrationstests geschrieben, um zu wissen, dass sie fast so schnell sein können wie die Unit-Tests.

Nachdem ich das Web durchgesehen hatte, stellte ich fest, dass es sehr ähnliche Ideen zu meinen gibt, die hier und da erwähnt werden . Was haltet ihr von dieser Idee?

Bearbeiten

Als ich auf die Fragen eines Beispiels antwortete, in dem das Design gut war, brauchte ich ein großes Refactoring für die nächste Anforderung:

Zuerst gab es einige Anforderungen, um bestimmte Befehle auszuführen. Ich habe einen erweiterbaren Befehlsparser geschrieben, der Befehle von einer Eingabeaufforderung aus analysierte und den richtigen Befehl für das Modell aufrief. Das Ergebnis wurde in einer Ansichtsmodellklasse dargestellt: Erstes Design

Hier war nichts falsch. Alle Klassen waren unabhängig voneinander und ich konnte leicht neue Befehle hinzufügen, neue Daten anzeigen.

Die nächste Anforderung war, dass jeder Befehl eine eigene Ansichtsdarstellung haben sollte - eine Art Vorschau des Befehlsergebnisses. Ich habe das Programm überarbeitet, um ein besseres Design für die neue Anforderung zu erzielen: Zweites Design

Das war auch gut so, denn jetzt hat jeder Befehl ein eigenes Ansichtsmodell und damit eine eigene Vorschau.

Die Sache ist, dass der Befehlsparser geändert wurde, um ein Token-basiertes Parsen der Befehle zu verwenden, und seine Fähigkeit, die Befehle auszuführen, wurde gestrippt. Jeder Befehl hat ein eigenes Ansichtsmodell, und das Datenansichtsmodell kennt nur das aktuelle Befehlsansichtsmodell, das dann die anzuzeigenden Daten kennt.

Ich wollte an dieser Stelle nur wissen, ob das neue Design keine bestehenden Anforderungen erfüllt. Ich musste keinen meiner Abnahmetests ändern. Ich musste fast JEDEN Komponententest überarbeiten oder löschen, was einen riesigen Haufen Arbeit darstellte.

Was ich hier zeigen wollte, ist eine häufige Situation, die während der Entwicklung häufig vorkam. Es gab kein Problem mit dem alten oder dem neuen Design, sie änderten sich einfach auf natürliche Weise mit den Anforderungen - wie ich es verstanden habe, ist dies ein Vorteil von TDD, dass sich das Design weiterentwickelt.

Fazit

Vielen Dank für alle Antworten und Diskussionen. Als Zusammenfassung dieser Diskussion habe ich mir einen Ansatz überlegt, den ich bei meinem nächsten Projekt testen werde.

  • Zunächst schreibe ich alle Tests, bevor ich etwas implementiere, wie ich es immer getan habe.
  • Für Anforderungen schreibe ich zunächst einige Abnahmetests, die das gesamte Programm testen. Dann schreibe ich einige Integrationstests für die Komponenten, in denen ich die Anforderung implementieren muss. Wenn es eine Komponente gibt, die eng mit einer anderen Komponente zusammenarbeitet, um diese Anforderung umzusetzen, würde ich auch einige Integrationstests schreiben, bei denen beide Komponenten zusammen getestet werden. Last but not least würde ich Unit-Tests für diese bestimmte Klasse schreiben, wenn ich einen Algorithmus oder eine andere Klasse mit hoher Permutation schreiben müsste, z. B. einen Serializer. Alle anderen Klassen werden nicht getestet, es werden jedoch Unit-Tests durchgeführt.
  • Bei Fehlern kann der Prozess vereinfacht werden. Normalerweise wird ein Fehler durch eine oder zwei Komponenten verursacht. In diesem Fall würde ich einen Integrationstest für die Komponenten schreiben, der den Fehler testet. Wenn es sich um einen Algorithmus handeln würde, würde ich nur einen Komponententest schreiben. Wenn es nicht einfach ist, die Komponente zu erkennen, in der der Fehler auftritt, würde ich einen Akzeptanztest schreiben, um den Fehler zu lokalisieren - dies sollte eine Ausnahme sein.
Yggdrasil
quelle
Diese Fragen scheinen eher das Problem zu lösen, warum überhaupt Tests geschrieben werden. Ich möchte diskutieren, ob das Schreiben von Funktionstests anstelle von Komponententests ein besserer Ansatz sein könnte.
Yggdrasil
pro meiner Lektüre, Antworten in doppelter Frage sind in erster Linie um , wenn nicht -Einheit Tests mehr Sinn machen
gnat
Dieser erste Link ist selbst ein Duplikat. Denken Sie, Sie meinen: programmers.stackexchange.com/questions/66480/…
Robbie Dee
In den Antworten des Links von Robbie Dee geht es noch mehr darum, warum überhaupt getestet werden sollte.
Yggdrasil

Antworten:

37

Es vergleicht Orangen und Äpfel.

Integrationstests, Abnahmetests, Komponententests, Verhaltenstests - das sind alles Tests, die Ihnen bei der Verbesserung Ihres Codes helfen, aber auch ganz andere.

Ich gehe meiner Meinung nach jeden der verschiedenen Tests durch und erkläre hoffentlich, warum Sie eine Mischung aus allen benötigen:

Integrationstests:

Testen Sie einfach, ob verschiedene Komponenten Ihres Systems korrekt integriert sind - beispielsweise simulieren Sie eine Webservice-Anfrage und überprüfen Sie, ob das Ergebnis zurückkommt. Ich würde im Allgemeinen reale (ish) statische Daten und verspottete Abhängigkeiten verwenden, um sicherzustellen, dass diese konsistent überprüft werden können.

Akzeptanztests:

Ein Akzeptanztest sollte direkt mit einem Geschäftsanwendungsfall korrelieren. Es kann riesig sein ("Trades werden korrekt übermittelt") oder winzig ("Filter filtert eine Liste erfolgreich") - es spielt keine Rolle; Was zählt, ist, dass es explizit an eine bestimmte Benutzeranforderung gebunden sein sollte. Ich konzentriere mich gerne auf diese für die testgetriebene Entwicklung, da dies bedeutet, dass wir ein gutes Referenzhandbuch mit Tests für User Stories haben, die von dev und qa überprüft werden müssen.

Unit-Tests:

Für kleine diskrete Funktionseinheiten, die für sich genommen eine einzelne User Story bilden können oder nicht, kann eine User Story, die besagt, dass wir alle Kunden abrufen, wenn wir auf eine bestimmte Webseite zugreifen, ein Akzeptanztest sein (simulieren Sie das Aufrufen des Webs) Seite und Überprüfung der Antwort), kann jedoch auch mehrere Komponententests enthalten (überprüfen Sie, ob die Sicherheitsberechtigungen überprüft wurden, ob die Datenbankverbindung korrekt abgefragt wurde, ob Code, der die Anzahl der Ergebnisse einschränkt, korrekt ausgeführt wurde) - dies sind alles "Komponententests". Das ist kein vollständiger Abnahmetest.

Verhaltenstests:

Definieren Sie, wie der Ablauf einer Anwendung bei einer bestimmten Eingabe aussehen soll. Beispiel: "Wenn keine Verbindung hergestellt werden kann, überprüfen Sie, ob das System die Verbindung erneut versucht." Auch hier ist es unwahrscheinlich, dass dies ein vollständiger Abnahmetest ist, aber Sie können trotzdem etwas Nützliches überprüfen.

Diese sind meiner Meinung nach alle durch viel Erfahrung im Schreiben von Tests entstanden; Ich mag es nicht, mich auf die Lehrbuchansätze zu konzentrieren, sondern auf das, was Ihren Tests Wert verleiht.

Michael
quelle
In Ihrer Definition gehe ich davon aus, dass ich mehr Verhaltenstests (?) Als Unit-Tests schreiben will. Unit Test ist für mich ein Test, der eine einzelne Klasse mit allen verspotteten Abhängigkeiten testet. Es gibt Fälle, in denen Unit-Tests am nützlichsten sind. Das ist zB wenn ich einen komplexen Algorithmus schreibe. Dann habe ich viele Beispiele mit erwarteter Ausgabe des Algorithmus. Ich möchte dies auf Geräteebene testen, da es tatsächlich schneller ist als der Verhaltenstest. Ich sehe nicht den Wert des Tests einer Klasse auf Einheitenebene, die nur eine Hand voll Pfade durch die Klasse hat, die leicht durch einen Verhaltenstest getestet werden können.
Yggdrasil
14
Ich persönlich halte Akzeptanztests für das Wichtigste, Verhaltenstests sind wichtig, um Dinge wie Kommunikation, Zuverlässigkeit und Fehlerfälle zu testen, und Unit-Tests sind wichtig, um kleine komplexe Features zu testen (Algorithmen wären ein gutes Beispiel dafür)
Michael
Ich bin mit Ihrer Terminologie nicht so fest. Wir programmieren eine Programmiersuite. Dort bin ich für den grafischen Editor zuständig. Meine Tests testen den Editor mit verspotteten Diensten aus dem Rest der Suite und mit verspotteter Benutzeroberfläche. Was für ein Test wäre das?
Yggdrasil
1
Hängt davon ab, was Sie testen - testen Sie Geschäftsfunktionen (Abnahmetests)? Testen Sie die Integration (Integrationstests)? Testen Sie, was passiert, wenn Sie auf eine Schaltfläche klicken (Verhaltenstests)? Testen Sie Algorithmen (Unit-Tests)?
Michael
4
"Ich mag es nicht, mich auf die Lehrbuchansätze zu konzentrieren, sondern auf das, was Ihren Tests Wert gibt." Oh, so wahr! Die erste Frage, die immer gestellt wird , lautet: "Welches Problem löse ich auf diese Weise?". Und verschiedene Projekte haben möglicherweise unterschiedliche Probleme zu lösen!
Laurent Bourgault-Roy
40

TL; DR: Solange es Ihren Bedürfnissen entspricht, ja.

Ich mache seit vielen Jahren die Entwicklung von Acceptance Test Driven Development (ATDD). Es kann sehr erfolgreich sein. Es gibt ein paar Dinge zu beachten.

  • Unit-Tests helfen wirklich dabei, das IOC durchzusetzen. Ohne Unit-Tests müssen die Entwickler sicherstellen, dass sie die Anforderungen an gut geschriebenen Code erfüllen (sofern Unit-Tests gut geschriebenen Code enthalten).
  • Sie können langsamer sein und falsche Fehler aufweisen, wenn Sie tatsächlich Ressourcen verwenden, die normalerweise verspottet sind.
  • Bei dem Test wird das spezifische Problem nicht genau wie bei Unit-Tests ermittelt. Sie müssen weitere Untersuchungen durchführen, um Testfehler zu beheben.

Nun die Vorteile

  • Viel bessere Testabdeckung, deckt Integrationspunkte ab.
  • Gewährleistet, dass das System als Ganzes die Akzeptanzkriterien erfüllt, worum es bei der Softwareentwicklung geht.
  • Erleichtert, beschleunigt und verbilligt große Refaktoren.

Wie immer liegt es an Ihnen, die Analyse durchzuführen und herauszufinden, ob diese Praxis für Ihre Situation geeignet ist. Im Gegensatz zu vielen Menschen glaube ich nicht, dass es eine idealisierte richtige Antwort gibt. Dies hängt von Ihren Bedürfnissen und Anforderungen ab.

dietbuddha
quelle
8
Hervorragender Punkt. Es ist allzu einfach, ein kleiner Weltraumkadett zu werden und Hunderte von Fällen zu schreiben, um ein warmes Glühen der Zufriedenheit zu bekommen, wenn es mit Bravour "bestanden" wird. Einfach ausgedrückt: Wenn Ihre Software aus Sicht des BENUTZERS nicht das tut, was sie tun muss, haben Sie den ersten und wichtigsten Test nicht bestanden.
Robbie Dee
Guter Punkt, um das spezifische Problem zu lokalisieren. Wenn ich eine große Anforderung habe, schreibe ich Abnahmetests, die das gesamte System testen, und schreibe dann Tests, die Teilaufgaben bestimmter Komponenten des Systems testen, um die Anforderung zu erfüllen. Mit diesem kann ich in den meisten Fällen die Komponente lokalisieren, in der der Defekt liegt.
Yggdrasil
2
"Unit Tests helfen IOC durchzusetzen"?!? Ich bin mir sicher, dass Sie DI anstelle von IoC meinten, aber warum sollte jemand die Verwendung von DI erzwingen wollen ? Persönlich finde ich, dass DI in der Praxis zu Nicht-Objekten führt (prozedurale Programmierung).
Rogério
Können Sie Ihre Meinung zu der (IMO-besten) Option, BEIDE Integrations- und Unit-Tests durchzuführen, im Vergleich zu dem Argument, nur Integrationstests durchzuführen, abgeben? Ihre Antwort hier ist gut, scheint diese Dinge aber als sich gegenseitig ausschließend einzustufen, was ich nicht glaube.
Starmandeluxe
@starmandeluxe Sie schließen sich ja nicht gegenseitig aus. Es handelt sich vielmehr um den Wert, den Sie aus dem Testen ableiten möchten. Ich würde Unit-Tests überall dort durchführen, wo der Wert die Entwicklungs- / Supportkosten für das Schreiben der Unit-Tests übersteigt. Ex. Ich würde mit Sicherheit die Zinseszinsfunktion in einer Finanzanwendung testen.
Dietbuddha
18

Nun, ich habe den TDD-ähnlichen Ansatz verwendet, um die Tests / Anforderungen das Design des Programms weiterentwickeln zu lassen. Das Problem war, dass mehr als die Hälfte der Entwicklungszeit für das Schreiben / Refactor-Tests

Unit-Tests funktionieren am besten, wenn sich die öffentliche Schnittstelle der Komponenten, für die sie verwendet werden, nicht zu oft ändert. Das heißt, wenn die Komponenten bereits gut konstruiert sind (z. B. nach den SOLID-Grundsätzen).

Zu glauben, dass ein gutes Design nur durch das "Werfen" vieler Komponententests entsteht, ist ein Trugschluss. TDD ist kein "Lehrer" für gutes Design, es kann nur ein bisschen helfen, um zu überprüfen, ob bestimmte Aspekte des Designs gut sind (insbesondere die Testbarkeit).

Wenn sich Ihre Anforderungen ändern und Sie die Interna einer Komponente ändern müssen und dies 90% Ihrer Komponententests zum Erliegen bringt, müssen Sie diese sehr oft überarbeiten, dann war das Design höchstwahrscheinlich nicht so gut.

Mein Rat lautet daher: Überlegen Sie sich, wie Sie die von Ihnen erstellten Komponenten konstruktiv gestalten und wie Sie sie nach dem Open / Closed-Prinzip weiterentwickeln können. Letzteres soll sicherstellen, dass die Funktionalität Ihrer Komponenten später erweitert werden kann, ohne sie zu ändern (und damit die von Ihren Komponententests verwendete API nicht beeinträchtigt). Solche Komponenten können (und sollten) durch Unit-Test-Tests abgedeckt werden, und die Erfahrung sollte nicht so schmerzhaft sein, wie Sie es beschrieben haben.

Wenn Sie ein solches Design nicht sofort entwickeln können, sind Akzeptanz- und Integrationstests in der Tat ein besserer Anfang.

BEARBEITEN: Manchmal ist das Design Ihrer Komponenten in Ordnung, aber das Design Ihrer Komponententests kann Probleme verursachen . Einfaches Beispiel: Sie wollen die Methode "MyMethod" der Klasse X testen und schreiben

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(Nehmen wir an, die Werte haben irgendeine Bedeutung).

Nehmen wir weiter an, dass es im Produktionscode nur einen Aufruf von gibt X.MyMethod. Für eine neue Anforderung benötigt die Methode "MyMethod" einen zusätzlichen Parameter (zum Beispiel so etwas wie context), der nicht weggelassen werden kann. Ohne Unit-Tests müsste man den aufrufenden Code an nur einer Stelle umgestalten. Bei Unit-Tests muss man 500 Stellen umgestalten.

Die Ursache ist jedoch nicht, dass das Gerät sich selbst testet, sondern dass derselbe Aufruf von "X.MyMethod" immer wieder wiederholt wird und nicht unbedingt dem Prinzip "Don't Repeat Yourself (DRY)" folgt Hier müssen Sie die Testdaten und die zugehörigen erwarteten Werte in eine Liste aufnehmen und die Aufrufe von "MyMethod" in einer Schleife ausführen (oder, wenn das Testtool sogenannte "Datenlaufwerktests" unterstützt, diese Funktion verwenden) Die Anzahl der Stellen, die in den Unit-Tests geändert werden sollen, wenn die Methodensignatur auf 1 geändert wird (im Gegensatz zu 500).

In Ihrem realen Fall mag die Situation komplexer sein, aber ich hoffe, Sie haben eine Idee: Wenn Ihre Komponententests eine Komponenten-API verwenden, für die Sie nicht wissen, ob Änderungen möglich sind, sollten Sie die Anzahl reduzieren von Aufrufen an diese API auf ein Minimum.

Doc Brown
quelle
„Das bedeutet, wenn die Komponenten , die bereits entwickelt sind gut.“: Ich stimme Ihnen zu, aber wie können die Komponenten , die bereits entwickelt werden , wenn man die Tests schreiben , bevor Sie den Code zu schreiben, und der Code ist das Design? Zumindest habe ich TDD so verstanden.
Giorgio
2
@Giorgio: Eigentlich ist es egal, ob du die Tests zuerst oder später schreibst. Entwerfen bedeutet, Entscheidungen über die Verantwortung einer Komponente, über die öffentliche Schnittstelle, über Abhängigkeiten (direkt oder injiziert), über Laufzeit- oder Kompilierverhalten, über Veränderbarkeit, über Namen, über Datenfluss, Kontrollfluss, Ebenen usw. zu treffen. Gut design bedeutet auch, einige entscheidungen auf den spätestmöglichen zeitpunkt zu verschieben. Unit-Test kann Ihnen indirekt zeigen, ob Ihr Design in Ordnung war: Wenn Sie viele davon nachträglich überarbeiten müssen, wenn sich die Anforderungen ändern, war dies wahrscheinlich nicht der Fall.
Doc Brown
@Giorgio: Ein Beispiel könnte klarstellen: Angenommen, Sie haben eine Komponente X mit einer Methode "MyMethod" und 2 Parametern. Mit TDD schreiben Sie, X x= new X(); AssertTrue(x.MyMethod(12,"abc"))bevor Sie die Methode tatsächlich implementieren. Bei Verwendung von Upfront-Design können Sie class X{ public bool MyMethod(int p, string q){/*...*/}}zuerst schreiben und die Tests später schreiben. In beiden Fällen haben Sie die gleiche Entwurfsentscheidung getroffen. Wenn die Entscheidung gut oder schlecht war, wird TDD Sie nicht informieren.
Doc Brown
1
Ich stimme Ihnen zu: Ich bin etwas skeptisch, wenn ich sehe, dass TDD blind angewendet wird, mit der Annahme, dass es automatisch gutes Design hervorbringt. Außerdem stört TDD manchmal, wenn das Design noch nicht klar ist: Ich bin gezwungen, die Details zu testen, bevor ich einen Überblick über meine Tätigkeiten habe. Wenn ich das richtig verstehe, sind wir uns einig. Ich denke, dass (1) Komponententests helfen, ein Design zu verifizieren, aber Design ist eine separate Aktivität, und (2) TDD ist nicht immer die beste Lösung, da Sie Ihre Ideen organisieren müssen, bevor Sie mit dem Schreiben von Tests beginnen, und TDD Sie verlangsamen kann diese.
Giorgio
1
In Kürze können Unit-Tests Fehler im internen Design einer Komponente aufzeigen. Die Schnittstellen-, Vor- und Nachbedingungen müssen vorher bekannt sein, sonst können Sie den Komponententest nicht erstellen. Daher muss das Design der Komponente, was die Komponente tut, ausgeführt werden, bevor der Komponententest geschrieben werden kann. Wie es das macht - das Design der unteren Ebene, das detaillierte Design oder das innere Design oder wie auch immer Sie es nennen möchten - kann stattfinden, nachdem der Komponententest geschrieben wurde.
Maarten Bodewes
9

Ja, natürlich ist es das.

Bedenken Sie:

  • Ein Unit-Test ist ein kleiner, gezielter Test, der einen kleinen Code ausführt. Sie schreiben viele davon, um eine anständige Codeabdeckung zu erzielen, sodass alle (oder die Mehrzahl der umständlichen Bits) getestet werden.
  • Ein Integrationstest ist ein umfangreicher Test, der eine große Oberfläche Ihres Codes testet. Sie schreiben nur wenige davon, um eine angemessene Codeabdeckung zu erzielen, sodass alle (oder die meisten der umständlichen Bits) getestet werden.

Sehen Sie den allgemeinen Unterschied ...

Das Problem ist die Codeabdeckung. Wenn Sie mithilfe von Integrations- / Akzeptanztests einen vollständigen Test Ihres gesamten Codes durchführen können, gibt es kein Problem. Ihr Code wird getestet. Das ist das Ziel.

Ich denke, Sie müssen sie möglicherweise verwechseln, da jedes TDD-basierte Projekt einige Integrationstests erfordert, um sicherzustellen, dass alle Einheiten tatsächlich gut zusammenarbeiten (ich weiß aus Erfahrung, dass eine zu 100% bestandene, auf Einheiten getestete Codebasis nicht unbedingt funktioniert wenn du sie alle zusammenstellst!)

Das Problem besteht darin, dass das Testen, Debuggen und Beheben von Fehlern sehr einfach ist. Einige Leute finden, dass ihre Komponententests sehr gut sind, sie sind klein und einfach und Fehler sind leicht zu erkennen, aber der Nachteil ist, dass Sie Ihren Code neu organisieren müssen, um ihn an die Komponententest-Tools anzupassen, und sehr viele von ihnen schreiben müssen. Es ist schwieriger, einen Integrationstest zu schreiben, um eine Menge Code abzudecken, und Sie müssen wahrscheinlich Techniken wie die Protokollierung verwenden, um Fehler zu debuggen (obwohl Sie dies sowieso tun müssen, können Sie keine Unit-Test-Fehler durchführen wenn vor Ort!).

In beiden Fällen erhalten Sie immer noch getesteten Code. Sie müssen nur entscheiden, welcher Mechanismus besser zu Ihnen passt. (Ich würde ein bisschen mischen, die komplexen Algorithmen testen und den Rest integrieren).

gbjbaanb
quelle
7
Nicht ganz richtig ... Bei Integrationstests können zwei Komponenten fehlerhaft sein, aber ihre Fehler werden im Integrationstest behoben. Es macht nicht viel aus, bis der Endbenutzer es in einer Weise verwendet, in der nur eine dieser Komponenten verwendet wird ...
Michael Shaw
1
Codeabdeckung! = Getestet - abgesehen von Fehlern, die sich gegenseitig auslöschen, was ist mit Szenarien, über die Sie noch nie nachgedacht haben? Integrationstests eignen sich gut für Happy Path-Tests, aber ich sehe selten angemessene Integrationstests, wenn die Dinge nicht gut laufen.
Michael
4
@Ptolemy Ich denke, die Seltenheit, dass 2 Buggy-Komponenten sich gegenseitig auslöschen, ist weitaus geringer als 2 funktionierende Komponenten, die sich gegenseitig stören.
gbjbaanb
2
@Michael, dann haben Sie einfach nicht genug Aufwand in das Testen gesteckt. Ich sagte, es ist schwieriger, gute Integrationstests durchzuführen, da die Tests viel detaillierter sein müssen. Sie können fehlerhafte Daten in einem Integrationstest genauso einfach bereitstellen wie in einem Komponententest. Integrationstest! = Glücklicher Weg. Es geht darum, so viel Code wie möglich zu trainieren. Aus diesem Grund gibt es Tools zur Codeabdeckung, die Ihnen zeigen, wie viel von Ihrem Code trainiert wurde.
gbjbaanb
1
@Michael Wenn ich Tools wie Cucumber oder SpecFlow richtig verwende, kann ich Integrationstests erstellen, die auch Ausnahmen und Extremsituationen so schnell wie Unit-Tests testen. Aber ich stimme zu, wenn eine Klasse zu viele Permutationen hat, schreibe ich lieber einen Komponententest für diese Klasse. Dies ist jedoch seltener der Fall als Klassen mit nur einer Handvoll Pfaden.
Yggdrasil
2

Ich halte das für eine schreckliche Idee.

Da Akzeptanztests und Integrationstests größere Teile Ihres Codes berühren, um ein bestimmtes Ziel zu testen, müssen sie im Laufe der Zeit mehr und nicht weniger überarbeitet werden. Schlimmer noch, da sie weite Teile des Codes abdecken, verbringen Sie mehr Zeit damit, die eigentliche Ursache aufzuspüren, da Sie einen breiteren Suchbereich haben.

Nein, Sie sollten normalerweise mehr Komponententests schreiben, es sei denn, Sie haben eine App mit 90% Benutzeroberfläche oder etwas anderes, das für Komponententests umständlich ist. Der Schmerz, auf den Sie stoßen, stammt nicht aus Unit-Tests, sondern aus der ersten Testentwicklung. Im Allgemeinen sollten Sie höchstens 1/3 Ihrer Zeit damit verbringen, Tests zu schreiben. Schließlich sind sie da, um Ihnen zu dienen, nicht umgekehrt.

Telastyn
quelle
2
Das Hauptrindfleisch, das ich gegen TDD höre, ist, dass es den natürlichen Entwicklungsfluss stört und die defensive Programmierung von Anfang an erzwingt. Wenn ein Programmierer bereits unter Zeitdruck steht, möchte er wahrscheinlich nur den Code ausschneiden und später polieren. Natürlich ist das Einhalten einer beliebigen Frist mit Buggy-Code eine falsche Wirtschaftlichkeit.
Robbie Dee
2
In der Tat, vor allem, weil "es später polieren" scheinbar nie wirklich passiert - jede Überprüfung, die ich mache, wo ein Entwickler das "es muss raus, wir machen es später" drängt, wenn wir alle wissen, dass das nicht passieren wird - technisch Schulden = bankrotte Entwickler meiner Meinung nach.
Michael
3
Die Antwort macht für mich Sinn, ich weiß nicht, warum es so viele Minuspunkte gab. Mike Cohn zitiert: "Unit-Tests sollten die Grundlage einer soliden Testautomatisierungsstrategie sein und als solche den größten Teil der Pyramide ausmachen. Automatisierte Unit-Tests sind wunderbar, weil sie einem Programmierer spezifische Daten liefern - es gibt einen Fehler und der ist aktiv Zeile 47 " mountaingoatsoftware.com/blog/…
guillaume31
4
@ guillaume31 Nur weil jemand einmal gesagt hat, dass sie gut sind, heißt das nicht, dass sie gut sind. Nach meiner Erfahrung werden Fehler NICHT durch Unit-Tests erkannt, da sie im Vorfeld auf die neue Anforderung umgestellt wurden. Die meisten Fehler sind auch Integrationsfehler. Deshalb erkenne ich die meisten bei meinen Integrationstests.
Yggdrasil
2
@Yggdrasil Ich könnte auch Martin Fowler zitieren: "Wenn Sie bei einem High-Level-Test einen Fehler bekommen, haben Sie nicht nur einen Fehler in Ihrem Funktionscode, sondern auch einen fehlenden Komponententest." martinfowler.com/bliki/TestPyramid.html Wie auch immer, wenn Integrationstests allein für Sie arbeiten, dann fein. Ich habe die Erfahrung gemacht, dass sie bei Bedarf langsamer sind, weniger präzise Fehlermeldungen liefern und weniger manövrierfähig (kombinatorischer) sind als Komponententests. Außerdem bin ich in der Regel weniger zukunftssicher, wenn ich Integrationstests schreibe - wenn ich über vorgefertigte (falsche?) Szenarien nachdenke und nicht über die Korrektheit eines Objekts an sich.
Guillaume31
2

Der "Gewinn" bei TDD ist, dass sobald die Tests geschrieben wurden, sie automatisiert werden können. Die Kehrseite ist, dass es einen erheblichen Teil der Entwicklungszeit verbrauchen kann. Ob dies den gesamten Prozess tatsächlich verlangsamt, ist umstritten. Das Argument ist, dass das Vorab-Testen die Anzahl der Fehler reduziert, die am Ende des Entwicklungszyklus behoben werden müssen.

Hier kommt BDD ins Spiel, da Verhaltensweisen in den Unit-Test einbezogen werden können, sodass der Prozess per Definition weniger abstrakt und greifbarer ist.

Wenn unendlich viel Zeit zur Verfügung stünde, würden Sie natürlich so viele Tests verschiedener Sorten wie möglich durchführen. Die Zeit ist jedoch im Allgemeinen begrenzt und kontinuierliche Tests sind nur bis zu einem gewissen Punkt kosteneffektiv.

Dies alles führt zu dem Schluss, dass die Tests, die den höchsten Wert liefern, im Vordergrund des Prozesses stehen sollten. Dies an sich begünstigt nicht automatisch eine Art von Prüfung gegenüber einer anderen - vielmehr muss jeder Fall einzeln geprüft werden.

Wenn Sie ein Befehlszeilen-Widget für den persönlichen Gebrauch schreiben, interessieren Sie sich hauptsächlich für Komponententests. Während ein Web-Service sagen würde, würde eine erhebliche Menge an Integration / Verhaltenstests erfordern.

Während sich die meisten Arten von Tests auf das konzentrieren, was man als "Rennstrecke" bezeichnen könnte, dh zu testen, was das Unternehmen heute benötigt, sind Unit-Tests hervorragend geeignet, um subtile Fehler zu beseitigen, die in späteren Entwicklungsphasen auftauchen könnten. Da dies ein Vorteil ist, der nicht leicht zu messen ist, wird er häufig übersehen.

Robbie Dee
quelle
1
Ich schreibe meine Tests im Voraus und ich schreibe genug Tests, um die meisten Fehler abzudecken. Wie für Bugs, die später auftauchen. Das klingt für mich so, als ob sich eine Anforderung geändert hat oder eine neue Anforderung ins Spiel kommt. Dann müssen die Integrations- / Verhaltenstests geändert und hinzugefügt werden. Wenn dann ein Fehler in einer alten Anforderung angezeigt wird, zeigt dies mein Test. Wie für die Automatisierung. Alle meine Tests laufen die ganze Zeit.
Yggdrasil
Das Beispiel, an das ich im letzten Absatz gedacht habe, ist, dass eine Bibliothek ausschließlich von einer einzigen Anwendung verwendet wurde, aber es damals eine Geschäftsanforderung gab, dies zu einer Universalbibliothek zu machen. In diesem Fall ist es möglicherweise besser, zumindest einige Komponententests durchzuführen, als neue Integrations- / Verhaltenstests für jedes System zu schreiben, das Sie an die Bibliothek anschließen.
Robbie Dee
2
Automatisierungstests und Komponententests sind eine völlig orthogonale Angelegenheit. Jedes Projekt mit Selbstachtung wird eine automatisierte Integration und Funktionstests haben. Zugegeben, es werden nicht oft manuelle Komponententests angezeigt, diese können jedoch vorhanden sein (im Grunde ist der manuelle Komponententest ein Testdienstprogramm für bestimmte Funktionen).
Jan Hudec
Na ja. Es gibt seit geraumer Zeit einen florierenden Markt für automatisierte Tools von Drittanbietern, die außerhalb des Entwicklungsbereichs existieren.
Robbie Dee
1

Der letzte Vorteil von Unit-Tests ist, dass sie schneller sind. Ich habe genug Integrationstests geschrieben, um zu wissen, dass sie fast so schnell sein können wie die Unit-Tests.

Dies ist der entscheidende Punkt und nicht nur "der letzte Vorteil". Wenn das Projekt immer größer wird, werden Ihre Integrationstests immer langsamer. Und hier meine ich so langsam, dass Sie aufhören werden, sie auszuführen.

Natürlich werden Unit-Tests auch langsamer, aber sie sind immer noch mehr als eine Größenordnung schneller. In meinem vorherigen Projekt (c ++, ungefähr 600 kLOC, 4000 Komponententests und 200 Integrationstests) dauerte es zum Beispiel ungefähr eine Minute, um alle und mehr als 15 auszuführen, um Integrationstests auszuführen. Das Erstellen und Ausführen von Komponententests für das zu ändernde Teil dauert im Durchschnitt weniger als 30 Sekunden. Wenn Sie es so schnell schaffen, wollen Sie es die ganze Zeit tun.

Nur um es klar zu machen: Ich sage nicht, Integrations- und Akzeptanztests hinzuzufügen, aber es sieht so aus, als hätten Sie TDD / BDD falsch gemacht.

Unit-Test sind auch gut für das Design.

Ja, wenn Sie die Testbarkeit berücksichtigen, wird das Design besser.

Das Problem war, dass mehr als die Hälfte der Entwicklungszeit für das Schreiben / Refactor-Tests. Am Ende wollte ich keine Features mehr implementieren, da ich viele Tests überarbeiten und schreiben musste.

Wenn sich die Anforderungen ändern, müssen Sie den Code ändern. Ich würde sagen, dass Sie Ihre Arbeit nicht beendet haben, wenn Sie keine Komponententests geschrieben haben. Das heißt aber nicht, dass Sie eine 100% ige Abdeckung mit Unit-Tests haben sollten - das ist nicht das Ziel. Einige Dinge (wie die Benutzeroberfläche oder der Zugriff auf eine Datei, ...) sind nicht einmal für den Unit-Test gedacht.

Das Ergebnis ist eine bessere Codequalität und eine weitere Testebene. Ich würde sagen, es lohnt sich.


Wir hatten auch mehrere 1000er-Abnahmetests und es würde eine ganze Woche dauern, bis alle durchgeführt waren.

BЈовић
quelle
1
Hast du mein Beispiel angeschaut? Das passierte die ganze Zeit. Der Punkt ist, wenn ich eine neue Funktion implementiere, ändere / füge ich den Komponententest hinzu, damit sie die neue Funktion testen - daher wird kein Komponententest unterbrochen. In den meisten Fällen habe ich Nebenwirkungen der Änderungen, die durch Unit-Tests nicht erkannt werden - weil die Umgebung verspottet wird. Meiner Erfahrung nach hat mir kein Komponententest jemals gesagt, dass ich ein vorhandenes Feature kaputt gemacht habe. Es waren immer die Integrations- und Akzeptanztests, die mir meine Fehler zeigten.
Yggdrasil
Wie für die Ausführungszeit. Bei einer wachsenden Anzahl von Anwendungen habe ich meist eine wachsende Anzahl von isolierten Komponenten. Wenn nicht, habe ich etwas falsch gemacht. Wenn ich eine neue Funktion implementiere, ist dies meist nur in einer begrenzten Anzahl von Komponenten der Fall. Ich schreibe einen oder mehrere Abnahmetests im Rahmen der gesamten Bewerbung - was langsam sein kann. Außerdem schreibe ich die gleichen Tests aus der Sicht der Komponenten - diese Tests sind schnell, da die Komponenten normalerweise schnell sind. Ich kann die Komponententests die ganze Zeit durchführen, weil sie schnell genug sind.
Yggdrasil
@Yggdrasil Wie ich schon sagte, Unit-Tests sind nicht alle mächtig, aber sie sind normalerweise die erste Testschicht, da sie die schnellsten sind. Andere Tests sind ebenfalls nützlich und sollten kombiniert werden.
BЈовић
1
Nur weil sie schneller sind, heißt das nicht, dass sie nur deswegen verwendet werden sollten oder weil es üblich ist, diese zu schreiben. Wie gesagt, meine Unit-Tests brechen nicht - also haben sie für mich keinen Wert.
Yggdrasil
1
Die Frage war, welchen Wert habe ich vom Unit Test, wenn sie nicht kaputt gehen? Warum sie schreiben, wenn ich sie immer an neue Anforderungen anpassen muss? Der einzige Wert, den ich von ihnen sehe, ist für Algorithmen und andere Klassen mit einer hohen Permutation. Dies sind jedoch weniger als die Komponenten- und Abnahmetests.
Yggdrasil