2016-03-07 14 views
5

Ich verwende CATCH v1.1 build 14, um Komponententests meines C++ Codes durchzuführen.Catch.hpp Komponententest: Wie können Testfälle dynamisch erstellt werden?

Als Teil des Tests möchte ich die Ausgänge mehrerer Module in meinem Code überprüfen. Es gibt keine festgelegte Anzahl von Modulen. Mehr Module können jederzeit hinzugefügt werden. Der Code zum Testen jedes Moduls ist jedoch identisch. Daher denke ich, es wäre ideal, den Testcode in eine for Schleife zu legen. In der Tat habe ich unter Verwendung von catch.hpp überprüft, dass ich Abschnitte innerhalb eines Testfalls dynamisch erstellen kann, wobei jeder Abschnitt einem Modul entspricht. Ich kann dies tun, indem Sie die SECTION Makro in einer for-Schleife umschließt, zum Beispiel:

#include "catch.hpp" 
#include <vector> 
#include <string> 
#include "myHeader.h" 

TEST_CASE("Module testing", "[module]") { 
    myNamespace::myManagerClass manager; 
    std::vector<std::string> modList; 
    size_t n; 

    modList = manager.getModules(); 
    for (n = 0; n < modList.size(); n++) { 
     SECTION(modList[n].c_str()) { 
      REQUIRE(/*insert testing code here*/); 
     } 
    } 
} 

(Dies ist kein vollständiges Arbeitsbeispiel, aber Sie bekommen die Idee.)

Hier ist mein Dilemma ist. Ich möchte die Module unabhängig testen, so dass bei Ausfall eines Moduls die anderen Module weiterhin getestet werden, anstatt den Test abzubrechen. Die Art, wie CATCH funktioniert, wird jedoch den gesamten Testfall abbrechen, wenn eine einzige REQUIRE fehlschlägt. Aus diesem Grund möchte ich für jedes Modul einen separaten Testfall erstellen, nicht nur einen separaten Abschnitt. Ich habe versucht, meine for Schleife außerhalb des TEST_CASE Makro setzen, aber dieser Code nicht zu kompilieren (wie ich erwartet hatte):

#include "catch.hpp" 
#include <vector> 
#include <string> 
#include "myHeader.h" 

myNamespace::myManagerClass manager; 
std::vector<std::string> modList; 
size_t n; 

modList = manager.getModules(); 
for (n = 0; n < modList.size(); n++) { 
    TEST_CASE("Module testing", "[module]") { 
     SECTION(modList[n].c_str()) { 
      REQUIRE(/*insert testing code here*/); 
     } 
    } 
} 

Es könnte möglich sein, dies main() von writing my own zu tun, aber ich kann nicht sehen, wie zu tun es genau. (Würde ich meinen TEST_CASE Code direkt in die main()? Was passiert, wenn ich meinen TEST_CASE Code in einer anderen Datei behalten? Auch wäre es meine anderen, Standardtestfälle auswirken?)

ich auch CHECK Makros verwenden kann anstelle von REQUIRE Makros zu vermeiden, den Testfall abzubrechen, wenn ein Modul fehlschlägt, aber dann bekomme ich das gegenteilige Problem: Es versucht, den Test auf einem Modul fortzusetzen, das früh ausfallen sollte. Wenn ich einfach jedes Modul in seinen eigenen Testfall legen könnte, würde das mir das ideale Verhalten geben.

Gibt es eine einfache Möglichkeit, Testfälle in CATCH dynamisch zu erstellen? Wenn ja, kannst du mir ein Beispiel geben, wie es geht? Ich habe die CATCH-Dokumentation gelesen und online gesucht, konnte jedoch keine Hinweise dazu finden.

Antwort

0

Es klingt wie Fang sein könnte Migration in Richtung des eigenschaftsbasierten Testens, was hoffentlich eine Möglichkeit zur dynamischen Erstellung von Testfällen ermöglicht. In der Zwischenzeit, hier ist, was ich getan habe.

Ich habe eine .cpp Datei mit einem einzigen TEST_CASE für ein einzelnes Modul und eine globale Variable für den Modulnamen erstellt. (Ja, ich weiß, globale Variablen sind böse, weshalb ich vorsichtig bin und mehr es als letztes Mittel verwendet wird):

module_unit_test.cpp:

#include "catch.hpp" 
#include <string> 
#include "myHeader.h" 

extern const std::string g_ModuleName; // global variable: module name 

TEST_CASE("Module testing", "[module]") { 
    myNamespace::myManagerClass manager; 
    myNamespace::myModuleClass *pModule; 
    SECTION(g_ModuleName.c_str()) { 
     pModule = manager.createModule(g_ModuleName.c_str()); 
     REQUIRE(pModule != 0); 
     /*insert more testing code here*/ 
    } 
} 

Dann erstelle ich eine ausführbare Datei, die diesen Test läuft auf einem einzelnen Modul in der Befehlszeile angegeben. (Ich habe versucht, unten durch die Catch::Session().run() Schleife, aber Catch lässt es nicht mehr als einmal ausgeführt werden.) Die Objektdatei aus dem Code unter module_test.cpp und aus dem Unit-Test-Code über module_unit_test.cpp sind beim Erstellen der ausführbaren Datei verknüpft.

module_test.cpp:

#define CATCH_CONFIG_RUNNER 
#include "catch.hpp" 
#include <string> 
#include <cstdio> 

std::string g_ModuleName; // global variable: module name 

int main(int argc, char* argv[]) { 
    // Make sure the user specified a module name. 
    if (argc < 2) { 
     std::cout << argv[0] << " <module name> <Catch options>" << std::endl; 
     return 1; 
    } 

    size_t n; 
    char* catch_argv[argc-1]; 
    int result; 

    // Modify the input arguments for the Catch Session. 
    // (Remove the module name, which is only used by this program.) 
    catch_argv[0] = argv[0]; 
    for (n = 2; n < argc; n++) { 
     catch_argv[n-1] = argv[n]; 
    } 

    // Set the value of the global variable. 
    g_ModuleName = argv[1]; 

    // Run the test with the modified command line arguments. 
    result = Catch::Session().run(argc-1, catch_argv); 

    return result; 
} 

Dann, ich in einem separaten ausführbaren das Looping (nicht auf die Objektdateien aus dem Code oben verlinkten):

module_test_all.cpp:

#include <cstdlib> 
#include <vector> 
#include <string> 
#include "myHeader.h" 

int main(int argc, char* argv[]) { 
    std::string commandStr; 
    int result, status = 0; 
    myNamespace::myManagerClass manager; 
    std::vector<std::string> modList; 
    size_t m, n; 

    // Scan for modules. 
    modList = manager.getModules(); 

    // Loop through the module list. 
    for (n = 0; n < modList.size(); n++) { 
     // Build the command line. 
     commandStr = "module_test " + modList[n]; 
     for (m = 1; m < argc; m++) { 
      commandStr += " "; 
      commandStr += argv[m]; 
     } 

     // Do a system call to the first executable. 
     result = system(commandStr.c_str()); 

     // If a test fails, I keep track of the status but continue 
     // looping so all the modules get tested. 
     status = status ? status : result; 
    } 

    return status; 
} 

Ja Es ist hässlich, aber ich habe bestätigt, dass es funktioniert.

1

Es gibt einen Weg zu erreichen, was Sie suchen, aber ich würde feststellen, dass Sie den falschen Weg, um darüber gehen: -

Unit-Tests sind jede Einheit zu testen, um gemeint, dh Sie Schreiben Sie eine Komponente und einen Test, um das korrekte Verhalten dieser Komponente zu überprüfen. Wenn Sie später entscheiden, eine Komponente auf irgendeine Weise zu ändern, aktualisieren Sie den entsprechenden Test.

Wenn Sie alle Ihre Tests für alle Ihre Komponenten in derselben Datei aggregieren, wird es viel schwieriger, die Einheit zu isolieren, die sich anders verhält.

Wenn Sie den Test einer Komponente ausklammern wollen, weil sie über alle Komponenten im Wesentlichen die gleiche ist, können Sie eine der folgenden Aktionen ausführen können:

1.Extrahieren Sie die gemeinsamen Tests in einen separaten Header-Datei

Sie können die Typnamen der Komponente, die Sie wünschen #define zu testen und dann schließen eine Header-Datei mit allen Tests darin:

// CommonTests.t.h 
#include "catch.hpp" 
TEST_CASE("Object Can be instantiated", "[ctor]") 
{ 
    REQUIRE_NOTHROW(COMPONENT component); 
} 

// SimpleComponent.t.cpp 
#define COMPONENT SimpleComponent 
#include "CommonTests.t.h" 

Dies ist Einfach zu tun, aber hat einen Nachteil - wenn Sie den Test-Runner ausführen, haben Sie doppelte Tests (namentlich), so dass Sie nur alle Tests oder Tags durchführen können.

Sie können dies lösen, indem Sie den Komponentennamen mit einem String versehen und ihn an den Testfallnamen anhängen.

** 2. Rufen gemeinsame Tests durch die Komponente Parametrisierung **

Ihre gemeinsame Tests in eine separate Datei Setzen und direkt die gemeinsamen Testmethoden nennen:

// CommonTests.t.h 
void RunCommonTests(ComponentInterface& itf); 

// CommonTests.t.cpp 
void RunCommonTests(ComponentInterface& itf) 
{ 
    REQUIRE(itf.answerToLifeUniverseAndEverything() == 42); 
} 

// SimpleComponent.t.cpp 
#include "SimpleComponent.h" 
#include "CommonTest.t.h" 
#include "catch.hpp" 

TEST_CASE("SimpleComponent is default-constructible", "[ctor]") 
{ 
    REQUIRE_NOTHROW(SimpleComponent sc); 
} 

TEST_CASE("SimpleComponent behaves like common components", "[common]") 
{ 
    SimpleComponent sc; 
    RunCommonTests(sc); 
} 
+0

Ich benutze Ihren ersten Vorschlag, und das bringt mich am meisten dorthin. Im Idealfall möchte ich Module zur Laufzeit dynamisch erkennen und jeweils einen Testfall generieren, aber ich fange an zu denken, dass es mit Catch nicht möglich ist. Ein Hintergrund: Die Module definieren abgeleitete Klassen aus einer abstrakten Basisklasse, die ich definiere. Sie erzeugen unterschiedliche Ausgänge, aber das Verfahren zur Überprüfung der Ausgänge ist gleich. Andere Entwickler fügen ihre eigenen Module hinzu, daher wäre es schön, wenn sie unsere Testfunktion nutzen könnten, um ihre eigenen Module auf Konformität zu prüfen. Vielleicht mache ich es falsch, aber ich sehe keinen besseren Weg. –

+1

Weitere Diskussionen hierzu finden Sie in der Google Group: https://groups.google.com/forum/#!searchin/catch-forum/section/catch-forum/mRBKqtTrITU/FoHEoMn3SN8J – JBRWilkinson

Verwandte Themen