单元测试gtest框架
背景
对单元测试gtest框架的系统性学习,也是对《C++程序设计实践与技巧测试驱动开发》读书笔记
代码下载地址
https://media.pragprog.com/titles/lotdd/code/lotdd-code.zip
常用宏
一些常用的宏
1.fixture 与 TEST宏与TEST_F宏
Google Mock允许我们定义一个fixture类,我们可以在这个类中为相关的测试声明函数和变量。从技术角度来说,所有的Google Mock测试都使用一个由Google Mock自己生成的fixture
fixture–一个跨测试可重用的类
如下,使用默认的fixture(用TEST宏)
如下,自己声明一个fixture(用TEST_F宏)
如果想让测试能够访问fixture类的成员变量,那么就需要将TEST宏替换成TEST_F宏
(尾部的F表示fixture)
测试用例的名称必须与fixture的名称一样。
2.EXPECT_THAT宏与ASSERT_THAT宏
ASSERT_THAT断言失败的话,其他代码不能继续执行
EXPECT_THAT断言失败的话,其他代码继续执行
ASSERT_THAT(实际值,期待值) 可以看出实际值在前
3.ASSERT_TRUE宏与ASSERT_FALSE宏
4.EXPECT_EQ宏与ASSERT_EQ宏
EXPECT_EQ(1, 1); // EQ是equals的意思,判断两个对象是否相等
ASSERT_EQ(期待值,实际值) //此时,是期待值在前
EXPECT_GT(2, 0); // GT是greater then, 比 xx更大,通过是数值比较
EXPECT_FALSE( 3>5); // 断言结果是false,参数是返回类型bool的表达式或函数
EXPECT_TRUE( 3>1); // 断言结果是true,参数是返回类型bool的表达式或函数
EXPECT_STREQ(“aaa”, “aaa”); // STREQ 是string equals,判断两个字符串是否相等
4.DISABLED_前缀
在测试名称前加上DISABLED_前缀的话,会跳过执行这个测试
当运行的时候,会提示有跳过的测试
5.测试崩溃
如果希望遇到未处理的异常就让测试崩溃,可以用下面的命令行选项运行Google Mock:
–gtest_catch_exceptions=0
6.MATCHER_P宏
MATCHER_P自定义了一个匹配器,它接受一个参数。
7.SetUp()与TearDown()
如果测试用例中,所有测试需要一条或更多的相同的初始化语句,那么就可以将它们写在fixture类的初始化函数中。在Google Mock中,必须将此函数命名为SetUp(它覆写了基类::testing::Test中的虚函数)
TearDown()是SetUp()的逆过程,用于进行清理工作。
以下的fixture里使用了智能指针,这样下面的示例里就可以不用写TearDown()了
一些测试技巧
对书里提到的一些测试技巧的总结
1.运行测试的一个子集(–gtest_filter选项)
如果所有的测试都跑一遍,需要很长的时间,而为了频繁的测试当前的修改,我们可以只测试其中的一个子集(当前修改的那个)
如下图,跑完所有的,会直接跑22个单元测试:
假如当前我们只修改了ARetweetCollectionWithOneTweet这个fixture的内容,只需要单独测试这个子集,可以用gtest_filter过滤出自己需要跑的单元测试。
过滤器的语法是:测试用例名.测试名称
如果想要运行一个特定的测试,可以如下图:
./test –gtest_filter=ARetweetCollectionWithOneTweet.IsNoLongerEmpty
最好不要养成每次只运行一个测试的习惯。可以使用通配符()运行多个测试。比如运行一下ARetweetCollectionWithOneTweet测试用例里的所有测试,如下图:
./test –gtest_filter=ARetweetCollectionWithOneTweet.
可以使用冒号(:)来分割不同的过滤器。
如果GoogleMock遇到一个负号(-),那么其后的所有过滤器匹配的测试都将被忽略。
如下图所示:
./test –gtest_filter=Retweet.:ATweet.:-ATweet*.Construct
注意:
-ATweet*.Construct告诉GoogleMock忽略名称中含有Construct的所有ATweet测试
测试思想
书里提到的一些测试思想,很有启发性
1.测试行为而非方法
TDD初学者常犯的一个错误是集中精力去测试成员函数。
比如实现了一个add()成员函数,再写一个TEST(ARetweetCollection,Add)的测试。但是,写一个完全覆盖add()行为的测试需要考虑多种不同的情形。结果就是你必须将许多不同的行为编码到同一个测试。此时,测试就没有文档价值了,同时,理解一个测试花费的时间也会增加。
相反,你要把注意力放在行为或描述行为的情形上。比如,如果加入一个之前已经加入过的tweet会发生什么?如果客户传入为空呢?等等,这样就能全面的查看测试名称,并且了解系统支持的确定行为。
2.保持简单
无谓的复杂性带来的成本是无止境的。相信你曾花费无数个小时去解读一个复杂的成员函数或错综复杂的设计。大多数时候,你都可以编写一个更简单的解决方案,从而节省每个人的时间。
有很多原因会导致开发人员制造出无谓的复杂性。
(1)时间压力
轻率使得复杂性不断累积,这将在多个方面(代码可理解性、修改代码的开销、构建时间)让你速度更慢。
(2)缺乏学习
在追求更好的代码的同时,也要承认自己制造的不良代码。这意味着你必须理解两者之间的差别。你可以通过团队成员结对编程或者审阅代码的方式获得真实的反馈。学习怎样识别设计缺陷和代码坏味。学习怎样以更好的方式去纠正它们,可以找几本关于创建简洁设计和代码的书本来学习。
(3)已有的复杂性
一个设计繁杂的已有代码库会让你在添加新行为时束手束脚。过长的方法会催生更冗长的方法,高耦合的设计也是孕育更加高耦合设计的温床。
(4)害怕去改代码
如果未经测试的话,你不会一直写出正确的代码。“如果没有出现问题,就不要去修复。”害怕修改代码会阻碍向更好的、可持续的设计进行重构。使用TDD的话,每个通过的测试都会提供改善代码的机会。
(5)臆测
“客户和账号之间可能存在多对多的关系,所以现在就把这个添加到系统中”或许大多数时候你是对的,但是你要承受过早引入的复杂性。有时候你会选择另外一个思路,这时,你将不断的为这个(无用的)额外的复杂性买单,或者也会花费大量的精力去移除这个没必要的复杂性。相反,你应该在真正需要的时候再考虑上述做法。通常,这样做不会带来额外的开销。
保持简单是你在持续变化的环境中的生存之道。
在敏捷开发里,每次迭代都会交付新特性,其中一些你可能从未考虑过。这是个挑战,如果系统不能适应新的改动,你就需要把新功能强加进系统。为此,最好的防范办法就是保持简单的设计:代码易读,没有冗余、没有无谓的复杂性。具有这些特征的系统会最大程度的降低维护成本。