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.
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
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
+50 Ottima risposta con buoni esempi! –
Nel mio caso aiutato nel passaggio da Xcode da libreria statica a file oggetto rilocabile in tipo Mach-O –