Wie sollte C ++ - Unit-Test-Code organisiert werden, um eine maximale Unit-Test-Effizienz zu erzielen?


47
  • Diese Frage bezieht sich nicht auf Unit Testing Frameworks.
  • Bei dieser Frage geht es nicht darum, Komponententests zu schreiben.
  • Bei dieser Frage geht es darum, wo der UT-Code geschrieben werden soll und wie / wann / wo er kompiliert und ausgeführt werden soll.

Das behauptet Michael Feathers, wenn er effektiv mit Legacy-Code arbeitet

gute Unit-Tests ... schnell laufen

und das

Ein Unit-Test, dessen Ausführung 1/10 Sekunde dauert, ist ein langsamer Unit-Test.

Ich halte diese Definitionen für sinnvoll. Ich denke auch, dass sie implizieren, dass Sie eine Reihe von Unit-Tests und eine Reihe von Code-Tests, die länger dauern, separat aufbewahren müssen, aber ich denke, das ist der Preis, den Sie bezahlen, wenn etwas nur einen Unit-Test nennt, wenn es (sehr) schnell läuft .

Offensichtlich besteht das Problem in C ++ darin, dass Sie zum "Ausführen" Ihrer Unit-Tests Folgendes tun müssen:

  1. Bearbeiten Sie Ihren Code (Produktion oder Komponententest, je nachdem, in welchem ​​"Zyklus" Sie sich befinden)
  2. Kompilieren
  3. Verknüpfung
  4. Start Unit Test Executable ( s )

Bearbeiten (nach seltsamer enger Abstimmung) : Bevor ich auf die Details eingehe, werde ich versuchen, den Punkt hier zusammenzufassen:

Wie kann C ++ Unit Test-Code effektiv organisiert werden, so dass es effizient ist, den (Test-) Code zu bearbeiten und den Testcode auszuführen?


Das erste Problem ist dann, zu entscheiden, wo der Unit-Test-Code abgelegt werden soll, damit:

  • Es ist "natürlich", sie in Kombination mit dem zugehörigen Produktionscode zu bearbeiten und anzuzeigen.
  • Es ist einfach / schnell, den Kompilierungszyklus für die Einheit zu starten, die Sie gerade ändern

Das zweite verwandte Problem ist dann, was zu kompilieren ist , damit die Rückmeldung sofort erfolgt.

Extreme Möglichkeiten:

  • Jede Unit-Test-Test-Unit befindet sich in einer separaten cpp-Datei und diese cpp-Datei wird separat kompiliert und (zusammen mit der Quellcode-Unit-Datei, die sie testet) mit einer einzelnen ausführbaren Datei verknüpft, die dann diesen einen Unit-Test ausführt.
    • (+) Dies minimiert die Startzeit (Kompilieren + Verknüpfen!) Für die einzelne Testeinheit.
    • (+) Der Test läuft super schnell, da nur eine Einheit getestet wird.
    • (-) Um die gesamte Suite auszuführen, müssen eine Unmenge von Prozessen gestartet werden. Kann ein Problem sein, zu handhaben.
    • (-) Der Overhead von Prozessstarts wird sichtbar
  • Die andere Seite wäre - noch - eine cpp-Datei pro Test, aber alle Test-cpp-Dateien (zusammen mit dem Code, den sie testen!) Sind zu einer ausführbaren Datei verknüpft (pro Modul / pro Projekt / nach Wahl).
    • (+) Die Kompilierungszeit wäre noch in Ordnung, da nur geänderter Code kompiliert wird.
    • (+) Das Ausführen der gesamten Suite ist einfach, da nur eine Exe ausgeführt werden muss.
    • (-) Das Verknüpfen der Suite wird einige Zeit in Anspruch nehmen, da jedes erneute Kompilieren eines Objekts ein erneutes Verknüpfen auslöst.
    • (-) (?) Der Anzug braucht länger, obwohl die Zeit in Ordnung sein sollte , wenn alle Unit-Tests schnell sind.

Wie werden C ++ - Komponententests in der Praxis gehandhabt? Wenn ich das Zeug nur jede Nacht / Stunde laufen lasse, spielt der zweite Teil keine Rolle, aber der erste Teil, nämlich wie man den UT-Code mit dem Produktionscode "koppelt", so dass es für Entwickler "natürlich" ist, beides beizubehalten Fokus ist immer wichtig, denke ich. (Und wenn Entwickler den UT-Code im Fokus haben, werden sie ihn ausführen wollen, was uns zu Teil zwei zurückführt.)

Geschichten und Erfahrungen aus der realen Welt werden geschätzt!

Anmerkungen:

  • Diese Frage lässt absichtlich eine nicht spezifizierte Plattform und ein nicht spezifiziertes Hersteller- / Projektsystem übrig.
  • Fragen mit dem Tag UT & C ++ ist ein guter Anfang, aber leider konzentrieren sich zu viele Fragen und insbesondere Antworten zu stark auf die Details oder auf bestimmte Frameworks.
  • Vor einiger Zeit beantwortete ich eine ähnliche Frage zur Struktur für Boost-Unit-Tests. Ich finde, dass diese Struktur für "echte", schnelle Unit-Tests fehlt. Und ich finde die andere Frage zu eng, daher diese neue Frage.

6
Eine weitere Komplikation ergibt sich aus der C ++ - Redewendung, dass so viele Fehler wie möglich zur Kompilierungszeit verschoben werden. Eine gute Suite von Komponententests muss häufig in der Lage sein, zu testen, dass bestimmte Verwendungszwecke nicht kompiliert werden können.

3
@Closers: Könnten Sie bitte die Argumente angeben, die Sie veranlasst haben, diese Frage zu schließen?
Luc Touraille

Ich verstehe nicht ganz, warum dies geschlossen werden musste. :-(Wo soll man nach Antworten auf solche Fragen suchen, wenn nicht in diesem Forum?

2
@ Joe: Warum brauche ich einen Komponententest, um herauszufinden, ob etwas kompiliert wird, wenn der Compiler mir das trotzdem mitteilt?
David Thornley

2
@David: Weil du sicherstellen willst, dass es nicht kompiliert wird . Ein kurzes Beispiel wäre ein Pipeline-Objekt, das eine Eingabe akzeptiert und eine Ausgabe erzeugt, Pipeline<A,B>.connect(Pipeline<B,C>)die kompiliert werden sollte, während Pipeline<A,B>.connect(Pipeline<C,D>)sie nicht kompiliert werden sollte: Der Ausgabetyp der ersten Stufe ist nicht kompatibel mit dem Eingabetyp der zweiten Stufe.
Sebastiangeiger

Antworten:


6

Wir haben alle Unit-Tests (für ein Modul) in einer ausführbaren Datei. Die Tests sind in Gruppen zusammengefasst. Ich kann einen einzelnen Test (oder einige Tests) oder eine Gruppe von Tests ausführen, indem ich einen (Test- / Gruppen-) Namen in der Befehlszeile des Testläufers eingebe. Das Build-System kann die Gruppe "Build" ausführen, die Testabteilung kann "All" ausführen. Der Entwickler kann einige Tests in eine Gruppe wie "BUG1234" einteilen, wobei 1234 die Issue-Tracker-Nummer des Falls ist, an dem er arbeitet.


6

Erstens bin ich nicht einverstanden mit "1) Bearbeiten Sie Ihren (Produktions-) Code und Ihren Komponententest". Sie sollten jeweils nur eine Änderung vornehmen. Wenn sich das Ergebnis ändert, wissen Sie nicht, welche Ursache dafür verantwortlich ist.

Ich stelle Unit-Tests gerne in einen Verzeichnisbaum, der den Hauptbaum beschattet. Wenn ich /sources/componentA/alpha/foo.ccund habe /objects/componentA/beta/foo.o, dann möchte ich so etwas wie /UTest_sources/componentA/alpha/test_foo.ccund /UTest_objects/componentA/beta/test_foo.o. Ich verwende den gleichen Schattenbaum für Stub / Mock-Objekte und alle anderen Quellen, die die Tests benötigen. Es wird einige Randfälle geben, aber dieses Schema vereinfacht die Dinge sehr. Ein gutes Editor-Makro kann mühelos die Testquelle neben der Subjektquelle aufrufen. Ein gutes Build-System (z. B. GNUMake) kann beide kompilieren und den Test mit einem Befehl ausführen (z. B. make test_foo) und eine Unmenge solcher Prozesse verwalten - nur diejenigen, deren Quellen sich seit dem letzten Test geändert haben - ganz einfach (ich habe es getan) Ich habe den Aufwand, diese Prozesse zu starten, nie als Problem empfunden. Es ist O (N).

Im selben Framework können Sie Tests in größerem Maßstab (keine Komponententests mehr) durchführen, die viele Objekte miteinander verknüpfen und viele Tests ausführen. Der Trick besteht darin, diese Tests nach der Dauer ihrer Erstellung / Ausführung zu sortieren und sie entsprechend in Ihren täglichen Zeitplan aufzunehmen. Führen Sie den Test mit einer Sekunde oder weniger aus, wann immer Sie Lust dazu haben. Starten Sie den Zehn-Sekunden-Test und dehnen Sie sich. Fünf-Minuten-Test und machen Sie eine Pause; halbstündiger Test und Mittagessen; Sechs-Stunden-Test und nach Hause gehen. Wenn Sie feststellen, dass Sie viel Zeit verschwenden, z. B. einen großen Test erneut verknüpfen, nachdem Sie nur eine kleine Datei geändert haben, machen Sie es falsch - selbst wenn die Verknüpfung sofort erfolgt, würden Sie immer noch einen langen Test ausführen, wenn dies der Fall ist wurde nicht verlangt.


ad (1) - ja, das wurde schlampig formuliert
Martin Ba
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.