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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM