Google單元測試框架gtest--值引數測試
測試一個方法,需要較多個引數進行測試,比如最大值、最小值、異常值和正常值。這中間會有較多重複程式碼工作,而值引數測試就是避免這種重複性工作,並且不會損失測試的便利性和準確性。
例如測試一個函式,需要些各種引數進行邊界測試,下面案例測試一個數是否為素數,需要測試各種引數。
方法1:
class Prime { public: bool IsPrime(int n) { if (n <= 1) return false; for (int i = 2; i * i <= n; i++) { // n is divisible by an integer other than 1 and itself. if ((n % i) == 0) return false; } return true; } }; TEST(IsPrimeTest, Negative) { Prime prime; EXPECT_FALSE(prime.IsPrime(-5)); EXPECT_FALSE(prime.IsPrime(-1)); } TEST(IsPrimeTest, Trivial) { Prime prime; EXPECT_FALSE(prime.IsPrime(0)); EXPECT_FALSE(prime.IsPrime(1)); EXPECT_TRUE(prime.IsPrime(2)); EXPECT_TRUE(prime.IsPrime(3)); }
輸出結果為:
上述測試並不為完整,但這其中就出現了很多重複的程式碼,比如每個test case都需要建立prime和寫EXPECT語句。
方法2:使用testfixture,可以減少建立prime類的操作。上面的測試建立prime就是待測資料初始化,如果準備初始化環境複雜,使用test fixtrue可以極大提高效率且保證每個test的執行條件一樣。
class PrimeTest : public ::testing::Test { protected: Prime prime; }; TEST_F(PrimeTest, Negative) { EXPECT_FALSE(prime.IsPrime(-5)); EXPECT_FALSE(prime.IsPrime(-1)); } TEST_F(PrimeTest, Trivial) { EXPECT_FALSE(prime.IsPrime(0)); EXPECT_FALSE(prime.IsPrime(1)); EXPECT_TRUE(prime.IsPrime(2)); EXPECT_TRUE(prime.IsPrime(3)); }
輸出結果為:
方法2比方法1改進很多,但是依然沒有改變程式碼重複的問題,繼續改進,使用迴圈。
方法3:使用迴圈消除重複程式碼。
class PrimeTest : public ::testing::Test { protected: Prime prime; }; TEST_F(PrimeTest, Negative) { auto vec = std::vector<int>{-5, -1, 0, 1}; for (auto v : vec) { EXPECT_FALSE(prime.IsPrime(v)); } } TEST_F(PrimeTest, Trivial) { auto vec2 = std::vector<int>{2, 3, 5, 7}; for_each(vec2.begin(), vec2.end(), [&](int a) { EXPECT_TRUE(prime.IsPrime(a)); } ); }
輸出結果為:
方法3消除了複製程式碼語句,可以完成同類型引數的測試,但是有一個大問題,很多引數公用一個測試用例,如果某個引數出錯,程式碼不能指示出是哪個test失敗了。
// 例如第一個用例有個引數寫錯了 TEST_F(PrimeTest, Negative) { auto vec = std::vector<int>{-5, -1, 0, 2}; for (auto v : vec) { EXPECT_FALSE(prime.IsPrime(v)); } }
輸出結果是第一個test出錯,只能提示到 PrimeTest.Negative失敗了。倘若引數列表有很多引數,那麼就不容易排查哪裡失敗了,這違背了測試的基本原則。
[ RUN ] PrimeTest.Negative D:\PROJECTS\googletest\googletest\samples\sample6_unittest.cc(297): error: Value of: prime.IsPrime(v) Actual: true Expected: false [ FAILED ] PrimeTest.Negative (1 ms)
比如方法1和方法2中的測試,同樣的錯誤可以給出如下詳細的錯誤提示。
[ RUN ] PrimeTest.Negative D:\PROJECTS\googletest\googletest\samples\sample6_unittest.cc(277): error: Value of: prime.IsPrime(2) Actual: true Expected: false [ FAILED ] PrimeTest.Negative (1 ms)
方法4:幸好gtest給出瞭解決方案,既能避免重複程式碼,又能每個測試單獨執行每個引數的測試用例,出錯後能準確的報告錯誤的位置。
class PrimeTest : public ::testing::TestWithParam<int> { protected: Prime prime; }; TEST_P(PrimeTest, ReturnsFalseForNonPrimes) { int n = GetParam(); EXPECT_FALSE(this->prime.IsPrime(n)); } INSTANTIATE_TEST_CASE_P(myParmTest, // Instance name PrimeTest, // Test case name testing::Values(-5,0,1,4)); // Type list
第一,PrimeTest繼承於TestWithParm<int>; 相當於建立了一個test suite。
第二,使用Test_P建立test case,第一個引數是test fixture類名,第二個引數test case 名。在測試case裡,可以使用GetParam獲取每個引數,使用this指標使用Prime類例項。
第三,註冊測試case,INSTANTIATE_TEST_CASE_P 巨集第一個引數是測試的名字,第二個引數是測試fixtue名字或test case名字。第三個引數是需要輸入到case裡執行的引數列表,使用values接收列表資料。
輸出結果如下圖,values(-5,0,1,4)合計4個引數,執行4個tests。
對比方法3,如果某個資料測試出錯,可以準備報告錯誤資訊。比如testing::Values(-5,0,1,5)),最後一個引數寫成5,輸出如下。提示最後一個用例test3,引數為5執行失敗.
myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3, where GetParam() = 5 (1 ms)
[==========] Running 4 tests from 1 test case. [----------] Global test environment set-up. [----------] 4 tests from myParmTest/PrimeTest [ RUN ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/0 [ OK ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/0 (0 ms) [ RUN ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/1 [ OK ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/1 (0 ms) [ RUN ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/2 [ OK ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/2 (0 ms) [ RUN ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3 D:\PROJECTS\googletest\googletest\samples\sample6_unittest.cc(320): error: Value of: this->prime.IsPrime(n) Actual: true Expected: false [ FAILED ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3, where GetParam() = 5 (1 ms)
方法5:上面方法4有個明顯缺點,test::Values列表的引數只能是符合test case條件的數,即全是測試false的數,如果需要一些素數測試結果也是true的測試,那麼就需要再寫個test case,這顯然不是很好的設計,那麼可以考慮把測試的結果true或false也當做引數傳遞個測試用例,這樣就可以在一個test case裡實現素數和非素數的測試工作。具體實現也很簡單,TestWithParam<T>,當T是一個組合數時,就實現了上述目標。
class PrimeTest : public ::testing::TestWithParam<std::pair<int, bool>>{ protected: Prime prime; }; TEST_P(PrimeTest, ReturnsFalseForNonPrimes) { auto parm = GetParam(); ASSERT_EQ(this->prime.IsPrime(parm.first), parm.second); } INSTANTIATE_TEST_CASE_P(myParmTest, PrimeTest, testing::Values(std::make_pair(-5, false), std::make_pair(-5, false), std::make_pair(0, false), std::make_pair(1, false), std::make_pair(4, false), std::make_pair(2, true), std::make_pair(3, true), std::make_pair(5, true) ));
輸出結果如下,8個測試用例,5個false和3個true的測試:
如果某個引數測試失敗,可以清晰的輸出測試錯誤資訊。
[ RUN ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3 D:\PROJECTS\googletest\googletest\samples\sample6_unittest.cc(342): error: Expected equality of these values: this->prime.IsPrime(parm.first) Which is: true parm.second Which is: false [ FAILED ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3, where GetParam() = (11, false) (1 ms)
尊重技術文章,轉載請註明!
Google單元測試框架gtest--值引數測試
https://www.cnblogs.com/pingwen/p/14459873.html