從場景設置一個彙集了其gtest
測試用例 不慎丟失在應用程序生成靜態鏈接庫。此外,GNU工具鏈正在使用中。
問題行爲的原因很簡單。測試 程序不包含庫中任何包含 TEST(X, just_a_passing_test)
的任何內容。因此,鏈接器不需要需要來鏈接該庫中的任何 目標文件以鏈接該程序。所以它沒有。因此,運行庫在可執行文件中找不到該測試,因爲它不在那裏。
它有助於理解GNU格式的靜態庫是對象文件的歸檔文件 ,裝飾有管家標題塊和全局符號表。
的OP發現,在程序中存在的問題庫中的特設參考編碼到 任何公共符號,他能「神奇地」迫使其 測試用例到程序中。
沒有魔力。爲了滿足對該公共符號的引用,鏈接器 現在有義務鏈接來自庫的對象文件 - 包含符號定義的對象文件。並且OP提供從.cpp
製作該庫的 。所以庫中只有一個目標文件,它也包含測試用例的定義。通過 鏈接中的目標文件,測試用例處於編程狀態。
該OP與編譯器選項徒勞無功,從GCC切換到鐺, 尋找更可敬的方式來實現相同的目的。編譯器與 無關。 GCC或鏗鏘聲,它會通過系統鏈接器ld
(除非採取非常措施取代它)來完成鏈接。
即使程序引用該對象文件中沒有符號,是否還有一種更可敬的方式可以使ld
鏈接靜態庫中的對象文件?
有。說的問題程序是app
和問題靜態庫是 libcool.a
然後鏈接app
通常GCC命令行類似於此,在相關 點:
g++ -o app -L/path/to/the/libcool/archive -lcool
這代表一個命令行來ld
,與額外的鏈接器選項和庫,g++
認爲它是缺省的系統,它發現自己。
當鏈接器來考慮-lcool
,它會發現這是一個請求 爲檔案/path/to/the/libcool/archive/libcool.a
。然後它會將 取出,無論此時它是否還有任何未解決的符號引用 ,其定義已在libcool.a
中的目標文件中編譯。如果有任何 ,那麼它會將這些目標文件鏈接到app
。如果沒有,那麼它將鏈接 什麼都不來自libcool.a
並傳遞。
但是我們知道libcool.a
中有符號定義,我們想要 鏈接,即使app
沒有引用它們。在這種情況下,我們可以告知 鏈接器鏈接來自libcool.a
的對象文件,即使它們未被引用也是 。更確切地說,我們可以告訴g++
告訴鏈接器要做到這一點, 像這樣:
g++ -o app -L/path/to/the/libcool/archive -Wl,--whole-archive -lcool -Wl,-no-whole-archive
那些-Wl,...
選項告訴g++
的選項傳遞給...
ld
。 --whole-archive
選項告訴ld
鏈接後續存檔中的所有對象文件,無論它們是否被引用,直到進一步通知。 -no-whole-archive
告訴 ld
停止這樣做,並照常恢復業務。
它可能看起來好像-Wl,-no-whole-archive
是多餘的,因爲它是在 g++
命令行的最後一件事。但事實並非如此。請記住,g++
在將系統默認庫 傳遞到ld
之前將其追加到命令行中。你肯定 不希望--whole-archive
這些默認庫鏈接時生效。 (鏈接將失敗並出現多個定義錯誤)。
應用此問題的解決方案的情況下和TEST(X, just_a_passing_test)
將被執行,而不強制程序作一些無操作 參考到定義該測試的對象文件的黑客攻擊。
這種解決方案在一般情況下有明顯的缺點。如果它發生從 庫,我們要強制鏈接的一些未被引用的目標文件包含 一堆其他未被引用的目標文件,我們真的不需要。 --whole-archive
將他們全部鏈接起來,他們只是在程序中膨脹。
的--whole-archive
解決方案可能會更可敬的是,無操作參考 劈,但它不是可敬。它甚至沒有看起來尊敬。
這裏真正的解決方案只是做合理的事情。如果你想 鏈接器鏈接你的程序中的東西的定義,然後不保留鏈接器 的祕密。至少聲明在每個編譯單元中, 希望使用它的定義。
做與gtest
測試情況下,合理的事情涉及的理解是 一個gtest
宏像TEST(X, just_a_passing_test)
擴展到類定義, 在這種情況下:
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 &);
};
(加上test_info_
靜態初始化和TestBody()
的定義)。
同樣爲TEST_F
,TEST_P
變體。因此,您可以在代碼中部署這些 宏,只需要將 應用於類定義的約束和期望相同。
鑑於此,如果你有cool.h
定義庫libcool
,在cool.cpp
實現,希望gtest
單元測試它,通過一個測試程序tests
是在tests.cpp
實現,合理的事情是執行: -
- 寫一個頭文件,
cool_test.h
- 在它
#include "cool.h"
#include <gtest/gtest.h>
在裏面。
- 然後在它
- 在
tests.cpp
#include "cool_test.h"
定義libcool
測試用例,
- 編譯和
libcool
和libgtest
鏈接tests.cpp
,爲什麼你不會做什麼OP做很明顯。你不會定義由tests.cpp
需要 類,不通過cool.cpp
需要,內tests.cpp
cool.cpp
不能及的。
的OP是反對的意見對定義庫中的測試用例 因爲:
怎麼回事,你將測試庫,而不必爲每一個, 使得快速的可執行文件運行他們的痛苦。
作爲一個經驗法則,我會建議保持每庫gtest
可執行 的做法進行單元測試:快速運行他們是無痛的平庸自動化工具 這樣的make
,這是遠遠更好地得到每個圖書館的合格/不合格判決結果僅僅是對一堆圖書館的判決。但是,如果你不想做,還是有什麼給 異議:
// 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();
}
編譯和鏈接與libcool
,libcooler
,libcoolest
和libgtest
感謝您的詳細信息。這有助於確認我的懷疑。最後,我使用每個庫的可執行目標來運行單元測試。 – NitrousUK
+50很好的例子,很好的回答! –
在我的案例中,幫助Xcode從靜態庫更改爲Mach-O類型的可重定位對象文件 –