2013-07-28 19 views
10

Ho un programma scritto in C++, con alcune sottocartelle contenenti librerie collegate. C'è un SConscript di livello superiore, che chiama i file SConscript nelle sottocartelle/librerie.GTest non trova test in unità di compilazione separate

All'interno di un cpp biblioteca, c'è un GTEST:

TEST(X, just_a_passing_test) { 
EXPECT_EQ(true, true); 
} 

C'è main() nella sorgente di programma di alto livello, che ha appena chiama GTests principali, e ha un altro GTEST:

int main(int argc, char** argv) { 
::testing::InitGoogleTest(&argc, argv); 
return RUN_ALL_TESTS(); 
} 

TEST(Dummy, should_pass){ 
EXPECT_EQ(true, true); 
} 

Ora il problema è che quando eseguo il programma, GTest esegue solo il test nel sorgente main.cpp. Ignorando il test nella libreria. Ora diventa bizzarro quando faccio riferimento a una classe non correlata nella stessa libreria cpp in main.cpp, senza alcun tipo di effetto collaterale (ad esempio 'SomeClass foo;'), il test appare magicamente. Ho provato a usare -O0 e altri trucchi per forzare gcc a non ottimizzare il codice che non viene chiamato. Ho persino provato Clang. Sospetto che abbia a che fare con il modo in cui GTest esegue il test durante la compilazione, ma non riesco a trovare alcuna informazione su questo problema. Credo che usi l'inizializzazione statica, quindi forse c'è qualche strano ordine in corso lì. Qualsiasi aiuto/informazione è molto apprezzato!

Aggiornamento: Trovato una sezione nelle FAQ che sembra simile a questo problema, nonostante si riferisca specificamente a Visual C++. Il che include un trucco/hack per forzare il compilatore a non scartare la libreria se non referenziata. Si consiglia di non mettere i test nelle librerie, ma questo mi lascia chiedermi in che altro modo testereste le librerie, senza avere un eseguibile per ognuno, rendendole rapidamente un problema e con un output eccessivo. https://code.google.com/p/googletest/wiki/Primer#Important_note_for_Visual_C++_users

risposta

11

Dalla scena-regolazione si coglie che la biblioteca il cui gtest banco di prova scompare è linkato staticamente nella build dell'applicazione. È anche in uso la toolchain GNU .

La causa del problema è semplice. Il test del programma non contiene riferimenti a qualcosa nella libreria che contiene TEST(X, just_a_passing_test). Quindi il linker non ha bisogno di per collegare qualsiasi file oggetto da quella libreria per collegare il programma. Quindi non è così. Quindi il runtime gtest non trova quel test nell'eseguibile, perché non è lì.

È utile comprendere che una libreria statica in formato GNU è un archivio di file oggetto, adornato da un blocco di intestazioni di casa e da una tabella di simboli globale.

L'OP ha scoperto che codificando nel programma ad hoc riferimento a qualsiasi simbolo pubblica nella biblioteca problema, poteva "magicamente" costringere il suo caso di prova nel programma.

Nessuna magia. Per soddisfare il riferimento a quel simbolo pubblico, il linker è ora obbligato a collegare un file oggetto dalla libreria - quello che contiene la definizione del simbolo. E l'OP comunica che la libreria viene creata da un .cpp. Quindi c'è un solo file oggetto nella libreria, e lo contiene anche la definizione del caso di test. Con quel file oggetto nel collegamento , il test case è in programma.

L'OP girava invano con le opzioni del compilatore, passando da GCC a clang, alla ricerca di un modo più rispettabile per raggiungere lo stesso fine. Il compilatore è irrilevante. GCC o clang, ottiene il suo collegamento fatto dal linker di sistema, ld (a meno che non siano state adottate misure insolite per sostituirlo).

Esiste un modo più rispettabile per ottenere ld per collegare un file oggetto da una libreria statica anche quando il programma non fa riferimento a nessun simbolo in tale file oggetto?

C'è. Dire il programma problema è app e la libreria statica problema è libcool.a

Poi la solita linea di comando GCC che collega app assomiglia a questo, nelle relative punti:

g++ -o app -L/path/to/the/libcool/archive -lcool 

Questo delegati una linea di comando per ld, con opzioni di linker addizionali e librerie che g++ considera predefinite per il sistema in cui si trova.

Quando il linker giunge a considerare -lcool, si scoprirà che questa è una richiesta per l'archivio /path/to/the/libcool/archive/libcool.a. Quindi verrà indicato se a questo punto sono ancora presenti riferimenti di simboli non risolti in mano le cui definizioni sono compilate in file oggetto in libcool.a. Se ci sono , allora collegherà questi file oggetto a app. In caso contrario, collega nulla da libcool.a e passa.

Ma sappiamo che ci sono definizioni di simboli in libcool.a che vogliamo il collegamento , anche se app non si riferisce a loro. In tal caso, è possibile comunicare allo il linker per collegare i file oggetto da libcool.a anche se non si fa riferimento a . Più precisamente, possiamo dire g++ per dire al linker per farlo, in questo modo:

g++ -o app -L/path/to/the/libcool/archive -Wl,--whole-archive -lcool -Wl,-no-whole-archive 

Quei -Wl,... opzioni dicono g++ di passare le opzioni ... a ld. L'opzione indica a ld di collegare tutti i file oggetto da archivi successivi, indipendentemente dal fatto che si faccia riferimento o meno a , fino a nuovo avviso. Il -no-whole-archive dice allo ld di smettere di farlo e riprendere gli affari come al solito.

Può sembrare che lo -Wl,-no-whole-archive sia ridondante, poiché è l'ultima cosa sulla riga di comando g++. Ma non lo è. Ricordare che g++ aggiunge le librerie predefinite di sistema alla riga di comando, dietro le quinte, prima di passarle allo ld. Sicuramente non vuoi che --whole-archive entri in vigore quando quelle librerie predefinite sono collegate. (Il collegamento fallirà con errori di definizione multipli).

Applicare questa soluzione al caso problema e verrà eseguito TEST(X, just_a_passing_test) , senza l'hack di forzare il programma per fare un po 'no-op riferimento nel file oggetto che definisce quel test.

C'è un evidente svantaggio di questa soluzione nel caso generale. Se accade che la libreria da che vogliamo forzare il collegamento di alcuni file oggetto senza riferimento contiene un gruppo di altri file oggetto senza riferimento che non abbiamo bisogno di . --whole-archive li collega tutti anche loro, e sono semplicemente gonfiati nel programma.

La soluzione --whole-archive può essere più rispettabile che il no-op di riferimento hack, ma non è rispettabili. Non ha nemmeno lo aspetto di tutto rispetto.

La vera soluzione qui è solo per fare la cosa ragionevole. Se si desidera il linker per collegare la definizione di qualcosa nel programma, quindi non mantenere questo un segreto da il linker. Almeno dichiarare la cosa in ciascuna unità di compilazione in cui si desidera che la sua definizione venga utilizzata.

Fare la cosa ragionevole con gtest test-casi comporta la comprensione che una macro gtest come TEST(X, just_a_passing_test) si espande ad una definizione di classe, in questo caso:

class X_just_a_passing_test_Test : public ::testing::Test { 
public: 
    X_just_a_passing_test_Test() {} 
private: 
    virtual void TestBody(); 
    static ::testing::TestInfo* const test_info_ __attribute__ ((unused)); 
    X_just_a_passing_test_Test(X_just_a_passing_test_Test const &); 
    void operator=(X_just_a_passing_test_Test const &); 
}; 

(più un inizializzatore statico per test_info_ e una definizione per TestBody()).

Allo stesso modo per le varianti TEST_F, TEST_P. Di conseguenza, è possibile distribuire questi macro nel proprio codice con gli stessi vincoli e le stesse aspettative che si applicherebbero alle definizioni di classe .

In questa luce, se si dispone di una libreria libcool definito cool.h, implementato in cool.cpp e volete gtest unit test per esso, deve essere eseguito da un programma di test tests che viene implementato in tests.cpp, la cosa ragionevole è: -

  • scrivere un file di intestazione, cool_test.h
  • #include "cool.h" in esso
  • #include <gtest/gtest.h> in esso.
  • quindi definire i vostri casi di test libcool in esso
  • #include "cool_test.h" in tests.cpp,
  • Compilare e collegare tests.cpp con libcool e libgtest

Ed è ovvio il motivo per cui non sarebbe fare quello che l'OP ha fatto. Non definirebbe classi che sono necessari da tests.cpp, e non necessaria per cool.cpp, entro cool.cpp e non in tests.cpp.

Il PO era contrario ai consigli contro la definizione dei casi di test nella biblioteca perché:

come altro si potrebbe verificare le librerie, senza avere un eseguibile per tutti, rendendo rapidamente correre loro un dolore .

Come regola generale mi sento di raccomandare la pratica di mantenere un gtest eseguibile per libreria da un'unità-testati: li esegue in modo rapido è indolore con strumenti di automazione banali tale make, ed è molto meglio per ottenere un verdetto pass/fail per libreria di solo un verdetto per un gruppo di librerie. Ma se non si vuole fare che non c'è ancora nulla alla obiezione:

// tests.cpp 
#include "cool_test.h" 
#include "cooler_test.h" 
#include "coolest_test.h" 

int main(int argc, char** argv) { 
    ::testing::InitGoogleTest(&argc, argv); 
    return RUN_ALL_TESTS(); 
} 

Compilare e collegare con libcool, libcooler, libcoolest e libgtest

+0

Grazie per le informazioni dettagliate. Questo aiuta a confermare i miei sospetti. Alla fine ho usato obiettivi eseguibili per ogni libreria per eseguire i test unitari. – NitrousUK

+0

+50 Ottima risposta con buoni esempi! –

+0

Nel mio caso aiutato nel passaggio da Xcode da libreria statica a file oggetto rilocabile in tipo Mach-O –