2013-07-28 37 views
10

我有一個用C++編寫的程序,其中一些子文件夾包含鏈接庫。有一個頂級SConscript,它在子文件夾/庫中調用SConscript文件。GTest沒有在獨立的編譯單元中找到測試

裏面庫CPP,有一個GTEST:

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

有主()在頂級節目源,這只是調用GTests爲主,並有另一個GTEST:

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

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

現在的問題是,當我運行程序時,GTest只運行main.cpp源代碼中的測試。忽略庫中的測試。現在,當我在main.cpp中以相同的庫cpp引用一個無關的類時,以一種無副作用的方式(例如'SomeClass foo;'),奇蹟般地出現了這個測試。我試過使用-O0和其他技巧來強制gcc不優化未調用的代碼。我甚至嘗試過Clang。 我懷疑這與GTest如何在編譯過程中測試發現有關,但我無法在此問題上找到任何信息。我相信它使用靜態初始化,所以也許有一些奇怪的命令在那裏進行。 任何幫助/信息非常感謝!

更新:在常見問題解答中找到了一段,聽起來像是這個問題,儘管它特指Visual C++。其中包括一個技巧/黑客迫使編譯器在不引用的情況下不放棄該庫。 它建議不要在庫中進行測試,但這會讓我想知道如果沒有每個庫的可執行文件,你會如何測試庫,這使得它們很快地運行起來很痛苦,並且輸出臃腫。 https://code.google.com/p/googletest/wiki/Primer#Important_note_for_Visual_C++_users

回答

11

從場景設置一個彙集了其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測試用例,
  • 編譯和libcoollibgtest

鏈接tests.cpp,爲什麼你不會做什麼OP做很明顯。你不會定義由tests.cpp需要 類,通過cool.cpp需要,內tests.cppcool.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(); 
} 

編譯和鏈接與libcoollibcoolerlibcoolestlibgtest

+0

感謝您的詳細信息。這有助於確認我的懷疑。最後,我使用每個庫的可執行目標來運行單元測試。 – NitrousUK

+0

+50很好的例子,很好的回答! –

+0

在我的案例中,幫助Xcode從靜態庫更改爲Mach-O類型的可重定位對象文件 –

相關問題