2016-03-07 14 views
5

Sto usando CATCH v1.1 build 14 per eseguire il test dell'unità del mio codice C++.Test dell'unità Catch.hpp: come creare dinamicamente i casi di test?

Come parte del test, vorrei controllare le uscite di diversi moduli nel mio codice. Non c'è un numero fisso di moduli; più moduli possono essere aggiunti in qualsiasi momento. Tuttavia, il codice per testare ciascun modulo è identico. Pertanto, penso che sarebbe ideale inserire il codice di prova in un ciclo for. Infatti, usando catch.hpp, ho verificato che posso creare in modo dinamico sezioni in un caso di test, in cui ogni sezione corrisponde a un modulo. Posso fare questo racchiudendo la macro SECTION in un ciclo for, per esempio:

#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*/); 
     } 
    } 
} 

(Questo non è un esempio di lavoro completo, ma si ottiene l'idea.)

Ecco il mio dilemma. Vorrei testare i moduli in modo indipendente, in modo tale che se un modulo si guasta, continuerà a testare gli altri moduli invece di interrompere il test. Tuttavia, il modo in cui CATCH funziona, interromperà l'intero Test Case se un singolo REQUIRE non riesce. Per questo motivo, vorrei creare un caso di test separato per ogni modulo, non solo una sezione separata. Ho provato a mettere il mio for ciclo di fuori del TEST_CASE macro, ma questo codice non riesce a compilare (come mi aspettavo):

#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*/); 
     } 
    } 
} 

Potrebbe essere possibile fare questo writing my ownmain(), ma non riesco a vedere come fare esattamente. (Avrei messo il mio codice TEST_CASE direttamente nel main()? Cosa succede se voglio mantenere il mio codice TEST_CASE in un file diverso? Inoltre, avrebbe sui miei altri, casi di test più standard?)

posso anche usare CHECK macro invece dei macro REQUIRE per evitare di interrompere il Test Case quando un modulo si guasta, ma poi ho il problema opposto: prova a continuare il test su un modulo che avrebbe dovuto fallire presto. Se potessi inserire ogni modulo nel proprio Test Case, questo dovrebbe darmi il comportamento ideale.

C'è un modo semplice per creare dinamicamente i casi di test in CATCH? Se è così, puoi darmi un esempio di come farlo? Ho letto la documentazione CATCH e ho cercato online, ma non ho trovato alcuna indicazione su come farlo.

risposta

0

suona come cattura potrebbe essere migrazione verso test basati su proprietà, che spero consentiranno un modo per creare dinamicamente i casi di test. Nel frattempo, ecco cosa ho finito per fare.

Ho creato un file .cpp con un singolo TEST_CASE per un singolo modulo e una variabile globale per il nome del modulo. (Sì, lo so variabili globali sono il male, che è il motivo per cui sto facendo attenzione e di utilizzarlo come ultima risorsa):

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*/ 
    } 
} 

Poi, creo un eseguibile che verrà eseguito questo test su un singolo modulo specificato sulla riga di comando. (Ho tentato di eseguire il looping di Catch::Session().run() qui sotto, ma Catch non consente di eseguirlo più di una volta.) Il file oggetto dal codice sotto module_test.cpp e dal codice di test dell'unità sopra module_unit_test.cpp sono collegati durante la creazione dell'eseguibile.

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; 
} 

Poi, faccio il loop in un eseguibile separato (non legato alle file oggetto dal codice di cui sopra):

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; 
} 

Sì , è brutto, ma ho confermato che funziona.

1

C'è un modo per ottenere ciò che stai cercando, ma mi piacerebbe osservare che si sta andando su questo nel modo sbagliato: -

test unitari sono a scopo di testare ogni unità, è cioè scrivere un componente e un test per verificare il corretto comportamento di quel componente. Se in seguito si decide di modificare un componente in qualche modo, si aggiorna il test corrispondente.

Se si aggregano tutti i test per tutti i componenti nello stesso file, diventa molto più difficile isolare l'unità che si comporta diversamente.

Se si vuole scomporre la sperimentazione di un componente, perché è essenzialmente lo stesso in tutti i componenti, si potrebbe fare una delle seguenti:

1.Estrarre i test comuni a un file di intestazione separata

È possibile #define il nome del tipo di componente che si desidera testare e quindi includere un file di intestazione con tutti i test in essa:

// 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" 

Questo è semplice da fare, ma ha uno svantaggio: quando si esegue il test runner, si avranno test duplicati (per nome) in modo da poter eseguire solo tutti i test o tag.

È possibile risolvere questo problema stringendo il nome del componente e pre/aggiungendolo al nome del caso di test.

** 2. chiamata test comuni di parametrizzazione del componente **

mettere il vostro test comuni in un file separato e chiamare i metodi di prova comuni direttamente:

// 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

Sto usando il tuo primo suggerimento, e questo mi fa quasi tutto il tragitto. Idealmente, mi piacerebbe rilevare dinamicamente i moduli in fase di esecuzione e generare un test case per ciascuno, ma sto iniziando a pensare che non sia possibile con Catch. Qualche background: i moduli definiscono le classi derivate da una classe base astratta che definisco. Producono uscite diverse, ma la procedura per controllare le uscite è la stessa. Altri sviluppatori aggiungono i propri moduli, quindi sarebbe bello se potessero usare la nostra funzione di test per verificare la conformità dei propri moduli. Forse sto sbagliando, ma non vedo un modo migliore. –

+1

C'è più discussione su questo nel Gruppo Google: https://groups.google.com/forum/#!searchin/catch-forum/section/catch-forum/mRBKqtTrITU/FoHEoMn3SN8J – JBRWilkinson

Problemi correlati