Arten von Komponententests basierend auf der Nützlichkeit

13

Wertmäßig sehe ich in meiner Praxis zwei Gruppen von Unit-Tests:

  1. Tests, die eine nicht triviale Logik testen. Das Schreiben (entweder vor oder nach der Implementierung) deckt einige Probleme / potenzielle Fehler auf und hilft, sicher zu sein, dass die Logik in Zukunft geändert wird.
  2. Tests, die eine sehr triviale Logik testen. Diese Tests ähneln eher dem Dokumentcode (normalerweise mit Mocks), als dass sie getestet werden. Der Wartungs-Workflow dieser Tests ist nicht "irgendeine Logik geändert, Test wurde rot - Gott sei Dank habe ich diesen Test geschrieben", sondern "irgendein trivialer Code geändert, Test wurde falsch negativ - ich muss den Test beibehalten (neu schreiben), ohne Gewinn zu erzielen". . Meistens sind diese Tests (außer aus religiösen Gründen) nicht zu pflegen. Und nach meiner Erfahrung mit vielen Systemen entsprechen diese Tests 80% aller Tests.

Ich versuche herauszufinden, was andere über das Thema der Wertetrennung bei Komponententests denken und wie es meiner Trennung entspricht. Was ich aber meistens sehe, ist entweder Vollzeit-TDD-Propaganda oder Tests-sind-nutzlos-nur-den-Code-schreiben-Propaganda. Ich interessiere mich für etwas in der Mitte. Ihre eigenen Gedanken oder Verweise auf Artikel / Papiere / Bücher sind willkommen.

SiberianGuy
quelle
3
Ich lasse Unit-Tests nach bekannten (spezifischen) Fehlern suchen - die einmal durch die ursprünglichen Unit-Tests gelaufen sind - als separate Gruppe, deren Aufgabe es ist, Regressionsfehler zu verhindern.
Konrad Morawski
6
Diese zweite Art von Tests sehe ich als eine Art "Reibungsänderung". Diskontieren Sie nicht ihre Nützlichkeit. Das Ändern selbst der Trivialitäten von Code hat in der Regel Welligkeitseffekte in der gesamten Codebasis, und das Einführen dieser Art von Reibung stellt eine Hürde für Ihre Entwickler dar, sodass sie nur die Dinge ändern, die sie wirklich benötigen, anstatt auf launischen oder persönlichen Vorlieben zu beruhen.
Telastyn
3
@ Telastyn - Alles an Ihrem Kommentar scheint mir völlig verrückt. Wer würde es absichtlich schwierig machen, den Code zu ändern? Warum sollten Entwickler davon abgehalten werden, Code nach Belieben zu ändern - vertrauen Sie ihnen nicht? Sind sie schlechte Entwickler?
Benjamin Hodgson
2
In jedem Fall, wenn das Ändern von Code zu "Welligkeitseffekten" neigt, hat Ihr Code ein Designproblem - in diesem Fall sollten Entwickler angehalten werden, so viel wie möglich zu überarbeiten. Fragile Tests halten Refactoring aktiv davon ab (ein Test schlägt fehl; wer kann sich die Mühe machen, herauszufinden, ob dieser Test einer der 80% der Tests war, die eigentlich nichts bewirken? Sie finden einfach einen anderen, komplizierteren Weg, dies zu tun). Aber Sie scheinen dies als eine wünschenswerte Eigenschaft anzusehen ... Ich verstehe es überhaupt nicht.
Benjamin Hodgson
2
Wie auch immer, das OP könnte diesen Blog-Beitrag des Erstellers von Rails interessant finden. Um seinen Standpunkt zu stark zu vereinfachen, sollten Sie wahrscheinlich versuchen, diese 80% der Tests wegzuwerfen.
Benjamin Hodgson

Antworten:

14

Ich denke, es ist ganz natürlich, bei Unit-Tests auf eine Kluft zu treffen. Es gibt viele verschiedene Meinungen darüber, wie man es richtig macht und natürlich sind alle anderen Meinungen von Natur aus falsch . In letzter Zeit gibt es eine ganze Reihe von Artikeln über DrDobbs, die sich mit diesem Thema befassen und auf die ich am Ende meiner Antwort verweise.

Das erste Problem, das ich bei Tests sehe, ist, dass es leicht ist, sie falsch zu verstehen. In meiner C ++ - Klasse am College waren wir sowohl im ersten als auch im zweiten Semester Unit-Tests ausgesetzt. In beiden Semestern wussten wir nichts über das Programmieren im Allgemeinen - wir versuchten, die Grundlagen des Programmierens über C ++ zu erlernen. Stellen Sie sich nun vor, Sie sagen den Schülern: "Oh, Sie haben einen kleinen jährlichen Steuerrechner geschrieben! Schreiben Sie jetzt einige Einheitentests, um sicherzustellen, dass es richtig funktioniert." Die Ergebnisse sollten offensichtlich sein - sie waren alle schrecklich, einschließlich meiner Versuche.

Wenn Sie zugeben, dass Sie beim Schreiben von Komponententests nicht gut drauf sind und besser werden möchten, werden Sie bald entweder mit trendigen Teststilen oder mit unterschiedlichen Methoden konfrontiert sein. Wenn ich Methoden teste, beziehe ich mich auf Vorgehensweisen wie Test-First oder das, was Andrew Binstock von DrDobbs tut, indem er die Tests neben den Code schreibt. Beide haben ihre Vor- und Nachteile, und ich lehne es ab, auf subjektive Details einzugehen, da dies einen Flammenkrieg auslösen wird. Wenn Sie sich nicht darüber im Klaren sind, welche Programmiermethode besser ist, kann der Teststil den Ausschlag geben. Sollten Sie TDD, BDD, Property-based Testing verwenden? JUnit verfügt über fortschrittliche Konzepte namens Theories, die die Grenze zwischen TDD- und eigenschaftsbasiertem Testen verwischen. Welche wann verwenden?

tl; dr Es ist einfach zu testen falsch zu bekommen, ist es unglaublich eigenwillig und ich glaube nicht , dass eine Testmethodik von Natur aus besser, solange sie fleißig genutzt und professionell im Rahmen , dass sie angemessen in weiteren Tests sind. In meinen Augen eine Ausweitung auf Behauptungen oder Hygienetests, die früher einen ausfallsicheren Ad-hoc-Ansatz für die Entwicklung gewährleistet haben, der jetzt viel, viel einfacher ist.

Aus subjektiven Gründen ziehe ich es vor, "Phasen" von Tests zu schreiben, da keine bessere Formulierung vorliegt. Ich schreibe Unit-Tests, in denen ich die Klassen isoliert teste, und benutze, wo nötig, Mocks. Diese werden wahrscheinlich mit JUnit oder ähnlichem ausgeführt. Dann schreibe ich Integrations- oder Abnahmetests, diese werden separat und meist nur wenige Male am Tag durchgeführt. Dies sind Ihre nicht-trivialen Anwendungsfälle. Normalerweise verwende ich BDD, da es nett ist, Features in natürlicher Sprache auszudrücken, was JUnit nicht so einfach bietet.

Schließlich Ressourcen. Diese werden widersprüchliche Meinungen präsentieren, die sich hauptsächlich auf Unit-Tests in verschiedenen Sprachen und mit unterschiedlichen Rahmenbedingungen konzentrieren. Sie sollten die Kluft zwischen Ideologie und Methodik aufzeigen und es Ihnen ermöglichen, sich Ihre eigene Meinung zu bilden, solange ich Ihre nicht zu sehr manipuliert habe :)

[1] The Corruption of Agile von Andrew Binstock

[2] Antwort auf die Antworten des vorherigen Artikels

[3] Reaktion auf die Verderbnis von Agile durch Onkel Bob

[4] Reaktion auf die Korruption von Agile durch Rob Myers

[5] Warum sich mit Gurkentests beschäftigen?

[6] Du machst es falsch

[7] Entferne dich von den Werkzeugen

[8] Kommentar zu 'Kata mit römischen Ziffern und Kommentar'

[9] Römische Ziffern Kata mit Kommentar

IAE
quelle
1
Eine meiner freundlichen Behauptungen wäre, wenn Sie einen Test schreiben, um die Funktion eines jährlichen Steuerrechners zu testen, dann schreiben Sie keinen Komponententest. Das ist ein Integrationstest. Ihr Rechner sollte in relativ einfache Ausführungseinheiten zerlegt werden, und Ihre Einheitentests testen diese Einheiten. Wenn eine dieser Einheiten nicht mehr ordnungsgemäß funktioniert (der Test schlägt fehl), wird ein Teil der Grundmauer herausgeschlagen, und Sie müssen den Code reparieren (im Allgemeinen nicht den Test). Entweder das, oder Sie haben ein Stück Code identifiziert, das nicht mehr benötigt wird und verworfen werden sollte.
Craig
1
@Craig: Genau! Das habe ich damit gemeint, dass ich nicht weiß, wie man richtige Tests schreibt. Als Student war der Steuereintreiber eine große Klasse, die ohne das richtige Verständnis von SOLID geschrieben wurde. Sie haben absolut Recht, dass dies mehr ein Integrationstest als alles andere ist, aber das war ein unbekannter Begriff für uns. Wir wurden nur von unserem Professor "Unit" -Tests ausgesetzt.
IAE
5

Ich halte es für wichtig, beide Arten von Tests zu haben und sie gegebenenfalls zu verwenden.

Wie Sie sagten, gibt es zwei Extreme und ich stimme ehrlich gesagt auch keinem zu.

Der Schlüssel ist, dass Unit-Tests Geschäftsregeln und -anforderungen abdecken müssen . Wenn das System das Alter einer Person nachverfolgen muss, schreiben Sie "triviale" Tests, um sicherzustellen, dass das Alter eine nicht negative Ganzzahl ist. Sie testen die vom System benötigte Datendomäne: Obwohl sie trivial ist, hat sie einen Wert, da sie die Parameter des Systems erzwingt .

Ebenso müssen sie bei komplexeren Tests Wert bringen. Sicher, Sie können einen Test schreiben, der etwas validiert, das keine Anforderung ist, aber irgendwo in einem Elfenbeinturm erzwungen werden sollte, aber es ist Zeit, Tests zu schreiben, die die Anforderungen validieren, für die der Kunde Sie bezahlt. Warum sollten Sie beispielsweise einen Test schreiben, der Ihren Code überprüft, um mit einem Eingabestream umzugehen, bei dem eine Zeitüberschreitung auftritt, wenn die einzigen Streams aus lokalen Dateien stammen und nicht aus dem Netzwerk?

Ich glaube fest an Unit-Tests und benutze TDD, wo immer es Sinn macht. Unit-Tests bringen sicherlich Wert in Form von höherer Qualität und "Fail-Fast" -Verhalten beim Ändern von Code. Es gibt jedoch auch die alte 80/20- Regel, die zu beachten ist. Irgendwann werden Sie beim Schreiben von Tests sinkende Renditen erzielen, und Sie müssen produktiver arbeiten, selbst wenn das Schreiben von Tests einen messbaren Wert hat.


quelle
Das Schreiben eines Tests, um sicherzustellen, dass ein System das Alter einer Person aufzeichnet, ist kein Einheitentest, IMO. Das ist ein Integrationstest. Ein Einheitentest würde die allgemeine Ausführungseinheit (auch als "Prozedur" bezeichnet) testen, die beispielsweise einen Alterswert aus einem Basisdatum und einem Offset in beliebigen Einheiten (Tagen, Wochen usw.) berechnet. Mein Punkt ist, dass ein bisschen Code keine seltsamen Abhängigkeiten vom Rest des Systems haben sollte. Es berechnet NUR ein Alter aus ein paar Eingabewerten, und in diesem Fall kann ein Komponententest das korrekte Verhalten bestätigen, was wahrscheinlich eine Ausnahme auslösen kann, wenn der Versatz ein negatives Alter ergibt.
Craig
Ich bezog mich nicht auf eine Berechnung. Wenn ein Modell ein Datenelement speichert, kann es überprüfen, ob die Daten zur richtigen Domäne gehören. In diesem Fall ist die Domäne die Menge der nichtnegativen Ganzzahlen. Berechnungen sollten im Controller (in MVC) erfolgen, und in diesem Beispiel wäre eine Altersberechnung ein separater Test.
4

Hier ist meine Meinung dazu: Alle Tests sind kostenpflichtig:

  • Anfangszeit und Aufwand:
    • Überlegen Sie, was Sie testen und wie Sie es testen
    • Implementieren Sie den Test und stellen Sie sicher, dass er das testet, was er soll
  • laufende Wartung
    • Stellen Sie sicher, dass der Test immer noch das tut, was er tun soll, da sich der Code natürlich weiterentwickelt
  • Test ausführen
    • Ausführungszeit
    • Analyse der Ergebnisse

Wir beabsichtigen auch, dass alle Tests Vorteile bieten (und meiner Erfahrung nach bieten fast alle Tests Vorteile):

  • Spezifikation
  • markieren Sie Eckfälle
  • Regression verhindern
  • automatische Überprüfung
  • Beispiele für die Verwendung von APIs
  • Quantifizierung spezifischer Eigenschaften (Zeit, Raum)

Es ist also ziemlich leicht zu erkennen, dass wenn Sie eine Reihe von Tests schreiben, diese wahrscheinlich einen gewissen Wert haben werden. Kompliziert wird dies, wenn Sie diesen Wert (den Sie übrigens möglicherweise nicht im Voraus kennen - wenn Sie Ihren Code wegwerfen, verlieren die Regressionstests ihren Wert) mit den Kosten vergleichen.

Jetzt ist Ihre Zeit und Mühe begrenzt. Sie möchten die Dinge tun, die den größten Nutzen zu den geringsten Kosten bieten. Und ich denke, das ist eine sehr schwierige Sache, nicht zuletzt, weil es Wissen erfordert, das man nicht hat oder teuer zu bekommen wäre.

Und das ist der eigentliche Unterschied zwischen diesen verschiedenen Ansätzen. Ich glaube, sie haben alle Teststrategien identifiziert, die von Vorteil sind. Jede Strategie hat jedoch im Allgemeinen unterschiedliche Kosten und Vorteile. Die Kosten und der Nutzen jeder Strategie werden wahrscheinlich stark von den Besonderheiten des Projekts, der Domäne und des Teams abhängen. Mit anderen Worten kann es mehrere beste Antworten geben.

In einigen Fällen bietet das Auspumpen von Code ohne Tests die besten Vorteile / Kosten. In anderen Fällen ist eine gründliche Testsuite möglicherweise besser. In anderen Fällen ist es möglicherweise am besten, das Design zu verbessern.


quelle
2

Was ist eigentlich ein Unit Test? Und gibt es hier wirklich eine so große Zweiteilung?

Wir arbeiten in einem Bereich, in dem das Lesen eines Buchstabens nach dem Ende eines Puffers ein Programm zum Absturz bringen oder zu einem völlig ungenauen Ergebnis führen kann offen, ohne dass ein direkter Beweis für den Fehler erbracht wird.

Es ist unmöglich, die gesamte Komplexität dieser Systeme zu beseitigen. Unsere Aufgabe ist es jedoch, diese Komplexität so gering wie möglich zu halten und zu verwalten.

Ist ein Komponententest ein Test, der bestätigt, dass beispielsweise eine Reservierung erfolgreich in drei verschiedenen Systemen gebucht wurde, ein Protokolleintrag erstellt und eine E-Mail-Bestätigung gesendet wurde?

Ich werde nein sagen . Das ist ein Integrationstest . Und diese haben definitiv ihren Platz, aber sie sind auch ein anderes Thema.

Ein Integrationstest bestätigt die Gesamtfunktion eines gesamten "Merkmals". Der Code hinter dieser Funktion sollte jedoch in einfache, testbare Bausteine ​​unterteilt werden, die auch als "Einheiten" bezeichnet werden.

Ein Komponententest sollte daher einen sehr begrenzten Umfang haben.

Dies bedeutet, dass der durch den Komponententest getestete Code einen sehr begrenzten Umfang haben sollte.

Dies bedeutet auch, dass eine der Säulen eines guten Designs darin besteht, Ihr komplexes Problem (soweit möglich) in kleinere Einzweckteile zu zerlegen, die relativ isoliert voneinander getestet werden können.

Am Ende steht ein System aus zuverlässigen Basiskomponenten, und Sie wissen, ob eine dieser grundlegenden Codeeinheiten kaputt geht, weil Sie einfache, kleine Tests mit begrenztem Umfang geschrieben haben, um genau das zu verdeutlichen.

In vielen Fällen sollten Sie wahrscheinlich auch mehrere Tests pro Einheit durchführen. Die Tests selbst sollten einfach sein und so weit wie möglich nur ein einziges Verhalten testen.

Die Vorstellung eines "Komponententests", bei dem nicht-triviale, ausgefeilte und komplexe Logik getestet wird, ist meiner Meinung nach ein Oxymoron.

Wenn es also zu einer solchen vorsätzlichen Zerlegung des Designs gekommen ist, wie in aller Welt könnte ein Unit-Test plötzlich zu Fehlalarmen führen, es sei denn, die Grundfunktion der getesteten Code-Unit hat sich geändert? Und wenn das passiert ist, sollten Sie besser glauben, dass es einige nicht offensichtliche Welleneffekte im Spiel gibt. Ihr fehlerhafter Test, der scheinbar ein falsches positives Ergebnis liefert, warnt Sie tatsächlich davor, dass durch eine Änderung ein größerer Kreis von Abhängigkeiten in der Codebasis aufgebrochen wurde. Er muss überprüft und behoben werden.

Einige dieser Einheiten (viele von ihnen) müssen möglicherweise mithilfe von Scheinobjekten getestet werden. Dies bedeutet jedoch nicht, dass Sie komplexere oder aufwändigere Tests schreiben müssen.

Zu meinem konstruiertes Beispiel eines Reservierungssystemes, kann man wirklich nicht sein Senden von Anforderungen aus , um eine Live - Reservierungsdatenbank oder Dritt-Service (oder sogar eine „dev“ Instanz davon) jedes Mal , wenn Sie geht zurück Einheit Test Code.

Sie verwenden also Mocks, die denselben Schnittstellenvertrag aufweisen. Die Tests können dann das Verhalten eines relativ kleinen, deterministischen Codeblocks validieren. Grün auf dem ganzen Brett sagt Ihnen dann, dass die Blöcke , aus denen sich Ihr Fundament zusammensetzt, nicht gebrochen sind.

Die Logik der einzelnen Unit-Tests selbst bleibt jedoch so einfach wie möglich.

Craig
quelle
1

Dies ist natürlich nur meine Meinung, aber nachdem ich in den letzten Monaten funktionales Programmieren in fsharp gelernt habe (mit C # -Hintergrund), habe ich ein paar Dinge erkannt.

Wie im OP angegeben, gibt es normalerweise zwei Arten von "Unit-Tests", die wir täglich sehen. Tests, die die Vor- und Nachteile einer Methode abdecken, die im Allgemeinen am wertvollsten sind, aber für 80% des Systems, bei dem es weniger um "Algorithmen" als um "Abstraktionen" geht, nur schwer durchzuführen sind.

Der andere Typ ist das Testen der Abstraktionsinteraktivität. Meiner Meinung nach sind diese Tests hauptsächlich aufgrund des Designs Ihrer Anwendung erforderlich. Wenn Sie sie ignorieren, riskieren Sie seltsame Bugs und Spagetti-Codes, weil die Leute nicht richtig über ihr Design nachdenken, es sei denn, sie sind gezwungen, die Tests zuerst durchzuführen (und bringen es selbst dann in der Regel durcheinander). Das Problem ist nicht so sehr die Testmethode, sondern das zugrunde liegende Design des Systems. Die meisten Systeme, die mit imperativen oder OO-Sprachen erstellt wurden, verlassen sich inhärent auf "Nebenwirkungen", auch bekannt als "Tun Sie dies, aber sagen Sie mir nichts". Wenn Sie sich auf den Nebeneffekt verlassen, müssen Sie ihn testen, da eine Geschäftsanforderung oder ein Geschäftsvorgang normalerweise ein Teil davon ist.

Wenn Sie Ihr System funktionaler gestalten, indem Sie Abhängigkeiten von Nebenwirkungen vermeiden und Zustandsänderungen / -nachverfolgung durch Unveränderlichkeit vermeiden, können Sie sich stärker auf "Ins and Outs" -Tests konzentrieren, die die Aktion klarer testen und weniger, wie Sie dorthin gelangen. Sie werden erstaunt sein, welche Vorteile Unveränderlichkeit für Sie hat, wenn Sie dieselben Probleme viel einfacher lösen können, und wenn Sie nicht mehr auf "Nebenwirkungen" angewiesen sind, können Sie Dinge wie Parallelisierung und asynchrone Programmierung fast ohne zusätzliche Kosten durchführen.

Seit ich mit dem Programmieren in Fsharp angefangen habe, brauche ich für nichts ein spöttisches Framework und habe sogar meine Abhängigkeit von einem IOC-Container ganz aufgegeben. Meine Tests basieren auf geschäftlichen Anforderungen und Werten und nicht auf umfangreichen Abstraktionsebenen, die normalerweise für die Komposition in der imperativen Programmierung erforderlich sind.

Mitchell Lee
quelle