Google Test(GTest)使用方法和源码解析——概况
? ? ? ? GTest是很多開源工程的測試框架。雖然介紹它的博文非常多,但是我覺得可以深入到源碼層來解析它的實現原理以及使用方法。這樣我們不僅可以在開源工程中學習到實用知識,還能學習到一些思想和技巧。我覺得有時候思想和技巧是更重要的。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 我們即將要分析的是GTest1.7版本。我們可以通過https://github.com/google/googletest.git得到代碼。
? ? ? ? 官方文檔見:
- https://github.com/google/googletest/blob/master/googletest/docs/primer.md
- https://github.com/google/googletest/blob/master/googletest/docs/advanced.md
? ? ? ? 我們先大致熟悉一下GTest的特性。GTest和很多開源工程一樣,并不只是針對特定的平臺,否則其使用范圍將大打折扣,所以GTest具有很好的移植特性和可復用性,我們以工程中的代碼為例
template <class T, typename Result>
Result HandleSehExceptionsInMethodIfSupported(T* object, Result (T::*method)(), const char* location) {
#if GTEST_HAS_SEH__try {return (object->*method)();} __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINTGetExceptionCode())) {std::string* exception_message = FormatSehExceptionMessage(GetExceptionCode(), location);internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure,*exception_message);delete exception_message;return static_cast<Result>(0);}
#else(void)location;return (object->*method)();
#endif // GTEST_HAS_SEH
}
? ? ? ? 這段代碼只是為了執行模板類T對象的method函數指針指向的方法。其核心就是(object->*method)()這句,但是它卻使用了20行的代碼去實現,就是為了解決平臺的兼容問題。從名字我們可以看出它為了兼容SEH機制——結構化異常處理——一種windows系統上提供給用戶處理異常的機制。而這種機制在linux系統上沒有。這個函數是GTest為移植特性所做工作的一個很好的代表,我們將在之后的源碼介紹中經常見到它的身影。
? ? ? ? 我們編碼時,有時候我們不僅考究邏輯的嚴謹,還非常注意編碼的風格和布局的優美。其實代碼就像一件作品,一個不負責任的作者可能是毫無章法的涂鴉手法,而有些有著一定境界的程序員可能就會按照自己的“畫風”去繪制——他的“畫風”可能你并不喜歡,但是那種風格卻是獨立和鮮明的,甚至是有一定道理的。而且如果一旦“畫風”確定,對于臨摹者來說只要照著這樣的套路去做,而不用自己發揮自己的風格,這對一個庫的發展也是非常有益的。 GTest同樣有著良好的組織結構,我們以其自帶的Sample1為例
// Tests factorial of negative numbers.
TEST(FactorialTest, Negative) {EXPECT_EQ(1, Factorial(-5));EXPECT_EQ(1, Factorial(-1));EXPECT_GT(Factorial(-10), 0);
}// Tests factorial of 0.
TEST(FactorialTest, Zero) {EXPECT_EQ(1, Factorial(0));
}// Tests factorial of positive numbers.
TEST(FactorialTest, Positive) {EXPECT_EQ(1, Factorial(1));EXPECT_EQ(2, Factorial(2));EXPECT_EQ(6, Factorial(3));EXPECT_EQ(40320, Factorial(8));
}
? ? ? ? 這段代碼是一套完整的測試代碼。可以觀察發現,每個邏輯使用一個TEST宏控制,其內部也是一系列EXPECT_*宏堆砌。先不論其他風格,單從整齊有規律的書寫方式上來說,GTest也算是一個便于結構性編碼的樣板。我們使用者只要照著這樣的樣板去編寫測試用例,是非常方便的,這也將大大降低我們使用GTest庫的門檻。
? ? ? ? TEST宏是一個很重要的宏,它構成一個測試特例。現在有必要介紹下其構成,TEST宏的第一個參數是“測試用例名”,第二個參數是“測試特例名”。測試用例(Test Case)是為某個特殊目標而編制的一組測試輸入、執行條件以及預期結果,以便測試某個程序路徑或核實是否滿足某個特定需求(引百度百科),測試特例是測試用例下的一組測試。以以上代碼為例,三段TEST宏構成的是一個測試用例——測試用例名是FactorialTest(階乘方法檢測,測試Factorial函數),該用例覆蓋了三種測試特例——Negative、Zero和Positive——即檢測輸入參數是負數、0和正數這三種特例情況。
? ? ? ? 我們再看一組檢測素數的測試用例
TEST(IsPrimeTest, Negative) {// This test belongs to the IsPrimeTest test case.EXPECT_FALSE(IsPrime(-1));EXPECT_FALSE(IsPrime(-2));EXPECT_FALSE(IsPrime(INT_MIN));
}// Tests some trivial cases.
TEST(IsPrimeTest, Trivial) {EXPECT_FALSE(IsPrime(0));EXPECT_FALSE(IsPrime(1));EXPECT_TRUE(IsPrime(2));EXPECT_TRUE(IsPrime(3));
}// Tests positive input.
TEST(IsPrimeTest, Positive) {EXPECT_FALSE(IsPrime(4));EXPECT_TRUE(IsPrime(5));EXPECT_FALSE(IsPrime(6));EXPECT_TRUE(IsPrime(23));
}
? ? ? ? 這組測試用例的名是IsPrimeTest(測試IsPrime函數),三個測試特例是Negative(錯誤結果場景)、Trivial(有對有錯的場景)和Positive(正確結果場景)。
? ? ? ? 對于測試用例名和測試特例名,不能有下劃線(_)。因為GTest源碼中需要使用下劃線把它們連接成一個獨立的類名
// Expands to the name of the class that implements the given test.
#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \test_case_name##_##test_name##_Test
? ? ? ? 這樣也就要求,我們不能有相同的“測試用例名和特例名”的組合——否則類名重合。
? ? ? ? 測試用例名和測試特例名的分開,使得我們編寫的測試代碼有著更加清晰的結構——即有相關性也有獨立性。相關性是通過相同的測試用例名聯系的,而獨立性通過不同的測試特例名體現的。我們通過這段測試代碼的運行結果查看一下這兩個特性
Running main() from gtest_main.cc
[==========] Running 6 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 3 tests from FactorialTest
[ RUN ] FactorialTest.Negative
[ OK ] FactorialTest.Negative (0 ms)
[ RUN ] FactorialTest.Zero
[ OK ] FactorialTest.Zero (0 ms)
[ RUN ] FactorialTest.Positive
[ OK ] FactorialTest.Positive (0 ms)
[----------] 3 tests from FactorialTest (0 ms total)[----------] 3 tests from IsPrimeTest
[ RUN ] IsPrimeTest.Negative
[ OK ] IsPrimeTest.Negative (0 ms)
[ RUN ] IsPrimeTest.Trivial
[ OK ] IsPrimeTest.Trivial (0 ms)
[ RUN ] IsPrimeTest.Positive
[ OK ] IsPrimeTest.Positive (0 ms)
[----------] 3 tests from IsPrimeTest (0 ms total)[----------] Global test environment tear-down
[==========] 6 tests from 2 test cases ran. (14 ms total)
[ PASSED ] 6 tests.
? ? ? ? 從輸出結果上,我們看到GTest框架將我們相同測試用例名的場景合并在一起,不同測試特例名的場景分開展現。而且我們還發現GTest有自動統計結果、自動格式化輸出結果、自動調度執行等特性。這些特性也將是之后博文分析的重點。? ? ?
? ? ? ? 雖然上例中,所有的執行都是正確的,但是如果以上測試中發生一個錯誤,也不能影響其他測試——不同測試用例不相互影響、相同測試用例不同測試特例不相互影響。我們稱之為獨立性。除了獨立性,也不失靈活性——一個測試測試特例中可以通過不同宏(ASSERT_*類宏會影響之后執行,EXPECT_*類宏不會)控制是否影響之后的執行。
? ? ? ? 如果我們編寫的測試用例組(如上例是兩組)中一組發生了錯誤,我們希望沒出錯的那組不用執行了,出錯的那組再執行一遍。一般情況下,我們可能需要去刪除執行正確的那段測試代碼,但是這種方式非常不優美——需要編譯或者忘記恢復代碼。GTest框架可以讓我們通過在程序參數控制執行哪個測試用例,比如我們希望只執行Factorial測試,就可以這樣調用程序
./sample1_unittest --gtest_filter=Factorial*
? ? ? ? 我們可以將以上特性稱之為選擇性測試。
? ? ? ? 最后一個特性便是預處理。我們測試時,往往要構造復雜的數據。如果我們在每個測試特例中都要構造一遍數據,將是非常繁瑣和不美觀的。GTest提供了一種提前構建數據的方式。我們以如下代碼為例
class ListTest : public testing::Test {protected:virtual void SetUp() {_m_list[0] = 11;_m_list[1] = 12;_m_list[2] = 13;}int _m_list[3];
};
TEST_F(ListTest, FirstElement) {EXPECT_EQ(11, _m_list[0]);
}TEST_F(ListTest, SecondElement) {EXPECT_EQ(12, _m_list[1]);
}TEST_F(ListTest, ThirdElement) {EXPECT_EQ(13, _m_list[2]);
}
? ? ? ? 我們讓ListTest類繼承于GTest提供的基類testing::Test,并重載SetUp方法。這樣我們每次執行ListTest的一個測試特例時,SetUp方法都會執行一次,從而將數據準備完畢。這樣我們只要在一個類中構建好數據就行了。這兒需要注意一下TEST_F宏,它的第一參數要求是類名——即ListTest——不像TEST宏的第一個參數我們可以隨便命名。
總結
以上是生活随笔為你收集整理的Google Test(GTest)使用方法和源码解析——概况的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WMI技术介绍和应用——总结(完)
- 下一篇: Google Test(GTest)使用