使用gtest完成單元測試

本文參考

CoderZh的技術博客
gtest官方文檔

1. 最簡單的測試

以下代碼來源于gtest的示例代碼

samples\sample1_unittest.cc

int Factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; i++) {
    result *= i;
  }
  return result;
}

TEST(FactorialTest, Negative) {
  // This test is named "Negative", and belongs to the "FactorialTest"
  // test case.
  EXPECT_EQ(1, Factorial(-5));
  EXPECT_EQ(1, Factorial(-1));
  EXPECT_GT(Factorial(-10), 0);
}

gtest實現測試功能,就是使用宏 TEST來定義一個結構 這個宏包含三部分

  1. 測試類名: “FactorialTest”
  2. 測試方法名:“Negative”
  3. 測試方法體:“EXPECT_EQ(1, Factorial(-5));......”

測試方法中通過比較實際值與期望值之間的關系來確定所需測試對象是否正常工作,例如判斷函數返回值等。
為此gtest 提供了一系列斷言宏。幫助用戶判斷一系列狀態。
總體上宏宏分為ASSERT系列與EXPECT系列

  1. ASSERT_* 斷言系列。當檢查點失敗時,退出當前函數。不執行后續檢測點
  2. EXPECT_*期望系列。當檢查點失敗時,繼續執行后續檢查點。

gtest 將斷言宏進行了分類

1.1. 真假檢查

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

1.2. 比較檢查

Fatal assertion Nonfatal assertion Verifies
ASSERT_EQ(val1,val2); EXPECT_EQ(val1,val2); val1 == val2
ASSERT_NE(val1,val2); EXPECT_NE(val1,val2); val1 != val2
ASSERT_LT(val1,val2); EXPECT_LT(val1,val2); val1 < val2
ASSERT_LE(val1,val2); EXPECT_LE(val1,val2); val1 <= val2
ASSERT_GT(val1,val2); EXPECT_GT(val1,val2); val1 > val2
ASSERT_GE(val1,val2); EXPECT_GE(val1,val2); val1 >= val2

1.3. 字符串檢查

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,_str_2); the two C strings have the same content
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); the two C strings have different content
ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); the two C strings have different content, ignoring case

為了更加清晰的認識各部分的意義,展開了宏定義,得到如下代碼。
vs2012 生成預處理文件方法為選擇源文件

屬性→c++→預處理器→預處理到文件→是(/P)
預處理取消顯示行號→是(/EP)

宏定義展開結果

class FactorialTest_Negative_Test : public ::testing::Test {
public:
    FactorialTest_Negative_Test() {}
private:
    virtual void TestBody();
    static ::testing::TestInfo* const test_info_ ;
    FactorialTest_Negative_Test(FactorialTest_Negative_Test const &);
    void operator=(FactorialTest_Negative_Test const &);
};
::testing::TestInfo* const FactorialTest_Negative_Test ::test_info_ = ::testing::internal::MakeAndRegisterTestInfo( "FactorialTest", "Negative", 0, 0, ::testing::internal::CodeLocation("samples\\sample1_unittest.cc", 79), (::testing::internal::GetTestTypeId()), ::testing::Test::SetUpTestCase, ::testing::Test::TearDownTestCase, new ::testing::internal::TestFactoryImpl< FactorialTest_Negative_Test>);

void FactorialTest_Negative_Test::TestBody() {
    switch (0) 
    case 0: 
    default:
        if (const ::testing::AssertionResult gtest_ar = (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "Factorial(-5)", 1, Factorial(-5)))) ;
        else 
            ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "samples\\sample1_unittest.cc", 82, gtest_ar.failure_message()) = ::testing::Message();
    
    switch (0)
    case 0:
    default:
        if (const ::testing::AssertionResult gtest_ar = (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(1)) == 1)>::Compare("1", "Factorial(-1)", 1, Factorial(-1)))) ;
        else
            ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "sample1_unittest.cc", 83, gtest_ar.failure_message()) = ::testing::Message();
    
    switch (0)
    case 0:
    default:
        if (const ::testing::AssertionResult gtest_ar = (::testing::internal::CmpHelperGT("Factorial(-10)", "0", Factorial(-10), 0))) ; 
        else
            ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "samples\\sample1_unittest.cc", 84, gtest_ar.failure_message()) = ::testing::Message();
}

從展開結果可以看出最終測試方法生成了一個
測試類名_測試方法名_Test的類
該類包含:

public的構造函數
禁用了拷貝構造函數與‘=’
靜態變量 test_info_
重載函數虛函數 TestBody

測試方法體被展開為TestBody

2. 包含初始化的測試

使用過JUnit測試的知道JUnit測試運行包含

  1. @BeforeClass setUpBeforeClass()
  2. @Before setUp()
  3. test()
  4. @After tearDown()
  5. @AfterClass tearDownAfterClass()

gtest 也支持類似操作
以下例子來自于

samples\sample5_unittest.cc

class QuickTest : public testing::Test {
 protected:
  virtual void SetUp() {
    start_time_ = time(NULL);
    test = 0;
  }
  virtual void TearDown() {
    const time_t end_time = time(NULL);
    EXPECT_TRUE(end_time - start_time_ <= 5) << "The test took too long.";
  }
  time_t start_time_;
  int test ;
};
TEST_F(QuickTest, test) {
    EXPECT_EQ(start_time_,start_time_);
}

宏展開如下

class QuickTest : public testing::Test {
 protected:  
  virtual void SetUp() {
    start_time_ = time(0);
    test = 0;
  }
  virtual void TearDown() {    
    const time_t end_time = time(0);           
    switch (0) 
    case 0: 
    default: 
      if (const ::testing::AssertionResult gtest_ar_ = ::testing::AssertionResult((end_time - start_time_ <= 5))) ; 
      else ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "\\sample5_unittest.cc", 81, ::testing::internal::GetBoolAssertionFailureMessage( gtest_ar_, "end_time - start_time_ <= 5", "false", "true").c_str()) = ::testing::Message() << "The test took too long.";
  }
  time_t start_time_;
  int test ;
};

class QuickTest_test_Test : public QuickTest { 
public: 
  QuickTest_test_Test() {} 
private: 
  virtual void TestBody(); 
  static ::testing::TestInfo* const test_info_ ; 
  QuickTest_test_Test(QuickTest_test_Test const &);
   void operator=(QuickTest_test_Test const &);
};

::testing::TestInfo* const QuickTest_test_Test ::test_info_ = ::testing::internal::MakeAndRegisterTestInfo( "QuickTest", "test", 0, 0, ::testing::internal::CodeLocation("\\sample5_unittest.cc", 89), (::testing::internal::GetTypeId<QuickTest>()), QuickTest::SetUpTestCase, QuickTest::TearDownTestCase, new ::testing::internal::TestFactoryImpl< QuickTest_test_Test>);

void QuickTest_test_Test::TestBody() {
    switch (0) 
    case 0: 
    default:
     if (const ::testing::AssertionResult gtest_ar = (::testing::internal:: EqHelper<(sizeof(::testing::internal::IsNullLiteralHelper(start_time_)) == 1)>::Compare("start_time_", "start_time_", start_time_, start_time_))) ;
     else ::testing::internal::AssertHelper(::testing::TestPartResult::kNonFatalFailure, "\\sample5_unittest.cc", 91, gtest_ar.failure_message()) = ::testing::Message();
}

這個示例代碼更好的體現出了測試類,測試方法之間的繼承關系
一個測試類包含多個測試方法,每個測試方法在執行前都先執行SetUpTestCase()然后執行TestBody()最后執行TearDownTestCase()

在實際測試中可以利用SetUpTestCase執行初始化,TearDownTestCase完成資源回收。確保每個案例相互獨立,不相互受影響。


__注意:帶有測試類的方法使用的宏是 TEST_F 不是 TEST __


3. 類級別共享數據

與JUnit一樣類級別共享的數據使用靜態變量實現,初始化在static void SetUpTestCase()完成,釋放在static void TearDownTestCase()完成

4.參數化測試

要實現參數化測試需要聲明類并繼承testing::TestWithParam<T>類,或testing::WithParamInterface<T>
二者關系為

template <typename T>
class TestWithParam : public Test, public WithParamInterface<T> {
};

參數化列表使用如下方法初始化

INSTANTIATE_TEST_CASE_P(TestState, DoubleStateTest, ::testing::Values(-1.0,0.0,1,0));

第一個參數是測試案例的前綴,可以任意取
第二個參數是測試案例的名稱,需要和之前定義的參數化的類的名稱相同
第三個參數是可以理解為參數生成器,上面的例子使用test::Values表示使用括號內的參數。Google提供了一系列的參數生成的函數:

函數名 說明
Range(begin, end[, step]) 范圍在begin~end之間,步長為step,不包括end
Values(v1, v2, ..., vN) v1,v2到vN的值
ValuesIn(container) and ValuesIn(begin, end) 從一個C類型的數組或是STL容器,或是迭代器中取值
Bool() 取false 和 true 兩個值
Combine(g1, g2, ..., gN) 將g1,g2,...gN進行排列組合,g1,g2,...gN本身是一個參數生成器,每次分別從g1,g2,..gN中各取出一個值,組合成一個元組(Tuple)作為一個參數。

運行參數化如下,GetParam()方法會依次返回參數列表中的參數

TEST_P(DoubleStateTest,TestGetDouble)
{
   doule state = GetParam();
}

注意: 參數化測試宏為 TEST_P


4. 一點示例

在我實現的測試方法中就存在如下關系。目的是合理組織測試用例。使得整個測試看起來有序。

//basetest.h
class BaseTest : public testing::Test{
public:
    static void SetUpTestCase() {
    m_pEngine = new MapEngine();
}
 static void TearDownTestCase() {
  delete m_engine;
  m_engine = NULL;
}
public:
    static MapEngine* m_pEngine;
};
//basetest.cpp
MapEngine BaseTest::m_pEngine= NULL;

//muictest.cc
class MiscTest : public BaseTest
{
public:
    virtual void SetUp()
    {
        m_pEngine->SetUp();
    }

    virtual void TearDown()
    {
        m_pEngine->TearDown();
    }
};
//需要參數化測試的案例
class BoolStateTest : public BaseTest,
    public testing::WithParamInterface<bool>{

}

5. 運行測試

在main 函數中

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

這樣即可執行所有測試

6. 運行參數

輸出xml
--gtest_output=xml:_path_to_output_file_

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容