使用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_

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容