it-swarm.com.de

Gibt es einen Grund, warum Tests nicht in Übereinstimmung mit dem Code geschrieben werden, den sie testen?

Ich habe kürzlich ein bisschen über Literate Programming gelesen und es hat mich zum Nachdenken gebracht ... Gut geschriebene Tests, insbesondere Spezifikationen im BDD-Stil, können besser erklären, was Code tut als Prosa und haben den großen Vorteil, ihre eigene Genauigkeit zu überprüfen.

Ich habe noch nie Tests gesehen, die inline mit dem Code geschrieben wurden, den sie testen. Liegt das nur daran, dass Sprachen es nicht einfach machen, Anwendungs- und Testcode zu trennen, wenn sie in dieselbe Quelldatei geschrieben sind (und niemand hat es einfach gemacht), oder gibt es einen grundlegenderen Grund, warum Benutzer Testcode vom Anwendungscode trennen?

92
Chris Devereux

Der einzige Vorteil, den ich mir für Inline-Tests vorstellen kann, wäre die Reduzierung der Anzahl der zu schreibenden Dateien. Mit modernen IDEs ist das wirklich keine so große Sache.

Inline-Tests weisen jedoch eine Reihe offensichtlicher Nachteile auf:

  • Es verstößt gegen Trennung von Bedenken . Dies mag umstritten sein, aber für mich ist das Testen der Funktionalität eine andere Verantwortung als das Implementieren.
  • Sie müssten entweder neue Sprachfunktionen einführen, um zwischen Tests/Implementierung zu unterscheiden, oder Sie riskieren, die Grenze zwischen beiden zu verwischen.
  • Größere Quelldateien sind schwieriger zu bearbeiten: schwerer zu lesen, schwerer zu verstehen, es ist wahrscheinlicher, dass Sie sich mit Konflikten bei der Quellcodeverwaltung befassen müssen.
  • Ich denke, es würde es schwieriger machen, sozusagen Ihren "Tester" -Hut aufzusetzen. Wenn Sie sich die Implementierungsdetails ansehen, werden Sie eher versucht sein, die Implementierung bestimmter Tests zu überspringen.
88
vaughandroid

Ich kann mir einige vorstellen:

  • Lesbarkeit. Das Einstreuen von "echtem" Code und Tests erschwert das Lesen des echten Codes.

  • Code aufblähen. Das Mischen von "echtem" Code und Testcode in dieselben Dateien/Klassen/was auch immer wahrscheinlich zu größeren kompilierten Dateien usw. führt. Dies ist besonders wichtig für Sprachen mit später Bindung.

  • Möglicherweise möchten Sie nicht, dass Ihre Kunden Ihren Testcode sehen. (Ich nicht wie aus diesem Grund ... aber wenn Sie an einem Closed-Source-Projekt arbeiten, ist es unwahrscheinlich, dass der Testcode dem Kunden trotzdem hilft.)

Jetzt gibt es mögliche Problemumgehungen für jedes dieser Probleme. Aber IMO, es ist einfacher, überhaupt nicht dorthin zu gehen.


Es ist erwähnenswert, dass in den frühen Tagen Java Programmierer diese Art von Dingen verwendeten, z. B. das Einfügen einer main(...) -Methode in eine Klasse, um das Testen zu erleichtern. Diese Idee hat Es ist in der Industrie üblich, Tests separat mit einem Testframework durchzuführen.

Es ist auch erwähnenswert, dass Literate Programming (wie von Knuth konzipiert) in der Software-Engineering-Branche noch nie Fuß gefasst hat.

36
Stephen C

Eigentlich können Sie sich Design By Contract so vorstellen. Das Problem ist, dass die meisten Programmiersprachen nicht zulassen, dass Sie Code wie folgt schreiben :( Es ist sehr einfach, die Voraussetzungen von Hand zu testen, aber die Post-Bedingungen sind eine echte Herausforderung, ohne die Art und Weise zu ändern, wie Sie Code schreiben (eine riesige negative IMO).

Michael Feathers hat ein Präsentation daz und dies ist eine der vielen Möglichkeiten, wie er erwähnt, dass Sie die Codequalität verbessern können.

14
Daniel Kaplan

Aus den gleichen Gründen, aus denen Sie versuchen, eine enge Kopplung zwischen Klassen in Ihrem Code zu vermeiden, ist es auch eine gute Idee, eine unnötige Kopplung zwischen Tests und Code zu vermeiden.

Erstellung : Tests und Code können zu unterschiedlichen Zeiten von unterschiedlichen Personen geschrieben werden.

Control : Wenn Tests verwendet werden, um Anforderungen festzulegen, möchten Sie sicher, dass für sie andere Regeln gelten, wer sie wann ändern kann als der eigentliche Code.

Wiederverwendbarkeit : Wenn Sie die Tests inline setzen, können Sie sie nicht mit einem anderen Code verwenden.

Stellen Sie sich vor, Sie haben einen Teil des Codes, der die Aufgabe korrekt erledigt, aber hinsichtlich Leistung, Wartbarkeit und was auch immer zu wünschen übrig lässt. Sie beschließen, diesen Code durch neuen und verbesserten Code zu ersetzen. Mithilfe derselben Tests können Sie überprüfen, ob der neue Code dieselben Ergebnisse wie der alte Code liefert.

Auswahlbarkeit : Wenn Sie die Tests vom Code trennen, können Sie leichter auswählen, welche Tests Sie ausführen möchten.

Beispielsweise verfügen Sie möglicherweise über eine kleine Suite von Tests, die sich nur auf den Code beziehen, an dem Sie gerade arbeiten, und über eine größere Suite, die das gesamte Projekt testet.

13
Caleb

Hier sind einige zusätzliche Gründe, die mir einfallen:

  • wenn Sie Tests in einer separaten Bibliothek haben, ist es einfacher, nur diese Bibliothek mit Ihrem Testframework und nicht mit Ihrem Produktionscode zu verknüpfen (dies könnte von einem Präprozessor vermieden werden, aber warum sollten Sie so etwas erstellen, wenn die einfachere Lösung darin besteht, die Tests einzuschreiben? ein separater Ort)

  • tests einer Funktion, einer Klasse oder einer Bibliothek werden normalerweise aus der Sicht eines "Benutzers" (eines Benutzers dieser Funktion/Klasse/Bibliothek) geschrieben. Ein solches "Verwenden von Code" wird normalerweise in eine separate Datei oder Bibliothek geschrieben, und ein Test kann klarer oder "realistischer" sein, wenn er diese Situation nachahmt.

10
Doc Brown

Wenn die Tests inline wären, müsste der Code entfernt werden, den Sie zum Testen benötigen, wenn Sie das Produkt an Ihren Kunden senden. Ein zusätzlicher Ort, an dem Sie Ihre Tests speichern, trennt einfach zwischen dem Code Sie Bedarf und dem Code, den Ihr Kunde benötigt.

5
mhr

Diese Idee läuft einfach auf eine "Self_Test" -Methode im Kontext eines objektbasierten oder objektorientierten Entwurfs hinaus. Wenn Sie eine kompilierte objektbasierte Sprache wie Ada verwenden, wird der gesamte Selbsttestcode vom Compiler während der Produktionskompilierung als nicht verwendet (nie aufgerufen) markiert und daher wird alles weg optimiert - nichts davon wird in der angezeigt resultierende ausführbare Datei.

Die Verwendung einer "Self_Test" -Methode ist eine äußerst gute Idee, und wenn sich Programmierer wirklich um Qualität kümmern würden, würden sie es alle tun. Ein wichtiges Problem ist jedoch, dass die "Self_Test" -Methode eine intensive Disziplin aufweisen muss, da sie nicht auf die Implementierungsdetails zugreifen kann und sich stattdessen nur auf alle anderen veröffentlichten Methoden innerhalb der Objektspezifikation stützen muss. Wenn der Selbsttest fehlschlägt, muss sich die Implementierung natürlich ändern. Der Selbsttest sollte alle veröffentlichten Eigenschaften der Objektmethoden rigoros testen, sich jedoch niemals auf Details einer bestimmten Implementierung stützen.

Objektbasierte und objektorientierte Sprachen bieten häufig genau diese Art von Disziplin in Bezug auf Methoden außerhalb des getesteten Objekts (sie erzwingen die Spezifikation des Objekts, verhindern den Zugriff auf seine Implementierungsdetails und lösen einen Kompilierungsfehler aus, wenn ein solcher Versuch erkannt wird ). Die internen Methoden des Objekts erhalten jedoch vollständigen Zugriff auf alle Implementierungsdetails. Die Selbsttestmethode befindet sich also in einer einzigartigen Situation: Sie muss aufgrund ihrer Natur eine interne Methode sein (Selbsttest ist offensichtlich eine Methode des zu testenden Objekts), muss jedoch die gesamte Compilerdisziplin einer externen Methode erhalten ( es muss unabhängig von den Implementierungsdetails des Objekts sein). Nur wenige Programmiersprachen bieten die Möglichkeit, die interne Methode eines Objekts so zu disziplinieren, als wäre es eine externe Methode. Dies ist also ein wichtiges Problem beim Design von Programmiersprachen.

In Ermangelung einer angemessenen Programmiersprachenunterstützung besteht der beste Weg, dies zu tun, darin, ein Begleitobjekt zu erstellen. Mit anderen Worten, für jedes Objekt, das Sie codieren (nennen wir es "Big_Object"), erstellen Sie auch ein zweites Begleitobjekt, dessen Name aus einem Standardsuffix besteht, das mit dem Namen des "echten" Objekts verknüpft ist (in diesem Fall "Big_Object_Self_Test") ") und deren Spezifikation aus einer einzelnen Methode besteht (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) return Boolean; "). Das Begleitobjekt hängt dann von der Spezifikation des Hauptobjekts ab, und der Compiler erzwingt die gesamte Disziplin dieser Spezifikation vollständig gegen die Implementierung des Begleitobjekts.

5
commenter8

Dies ist eine Reaktion auf eine große Anzahl von Kommentaren, die darauf hinweisen, dass Inline-Tests nicht durchgeführt werden, da es schwierig bis unmöglich ist, den Testcode aus Release-Builds zu entfernen. Das ist falsch. Fast alle Compiler und Assembler unterstützen dies bereits. Bei kompilierten Sprachen wie C, C++, C # erfolgt dies mit sogenannten Compiler-Direktiven.

Im Fall von c # (ich glaube auch von c ++ kann die Syntax je nach verwendetem Compiler leicht abweichen) können Sie dies so tun.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#Elif TEST
// test only code
#endif

Da hierfür Compiler-Direktiven verwendet werden, ist der Code in den ausführbaren Dateien, die erstellt werden, wenn die Flags nicht gesetzt sind, nicht vorhanden. Auf diese Weise erstellen Sie auch Programme "Einmal schreiben, zweimal kompilieren" für mehrere Plattformen/Hardware.

5
john

Wir verwenden Inline-Tests mit unserem Perl-Code. Es gibt ein Modul, Test :: Inline , das Testdateien aus dem Inline-Code generiert.

Ich bin nicht besonders gut darin, meine Tests zu organisieren, und habe festgestellt, dass sie einfacher und wahrscheinlicher zu warten sind, wenn sie inline sind.

Antwort auf einige der vorgebrachten Bedenken:

  • Die Inline-Tests sind in POD-Abschnitten geschrieben, sodass sie nicht Teil des eigentlichen Codes sind. Sie werden vom Interpreter ignoriert, sodass kein Code aufgebläht wird.
  • Wir verwenden Vim-Faltung , um die Testabschnitte auszublenden. Das einzige, was Sie sehen, ist eine einzelne Zeile über jeder getesteten Methode wie +-- 33 lines: #test----. Wenn Sie mit dem Test arbeiten möchten, erweitern Sie ihn einfach.
  • Das Test :: Inline-Modul "kompiliert" die Tests zu normalen TAP-kompatiblen Dateien, sodass sie mit herkömmlichen Tests koexistieren können.

Als Referenz:

2
mla

Ein weiterer Grund für die Trennung von Tests besteht darin, dass Sie häufig zusätzliche oder sogar andere Bibliotheken zum Testen verwenden als für die eigentliche Implementierung. Wenn Sie Tests und Implementierung mischen, kann der Compiler die versehentliche Verwendung von Testbibliotheken in der Implementierung nicht abfangen.

Außerdem haben Tests in der Regel viel mehr Codezeilen als die von ihnen getesteten Implementierungsteile, sodass Sie Probleme haben, die Implementierung zwischen allen Tests zu finden. :-)

1

Erlang 2 unterstützt tatsächlich Inline-Tests. Jeder boolesche Ausdruck im Code, der nicht verwendet wird (z. B. einer Variablen zugewiesen oder übergeben wird), wird automatisch als Test behandelt und vom Compiler ausgewertet. Wenn der Ausdruck falsch ist, wird der Code nicht kompiliert.

1
Mark Rendle

Das ist nicht wahr. Es ist viel besser, Ihre Unit-Tests neben dem Produktionscode zu platzieren, wenn der Produktionscode, insbesondere wenn die Produktionsroutine rein ist.

Wenn Sie beispielsweise unter .NET entwickeln, können Sie Ihren Testcode in die Produktionsassembly einfügen und diese dann vor dem Versand mit Scalpel entfernen.

0
zumalifeguard