Google Test的高阶使用
[TOC]
本文将官网上的高阶用法整理了一下,并在整理好总结了对此优秀开源测试框架的感悟.
总结感悟
- 测试相关
- 建立测试用例的层次
- 保证测试用例间必须独立互不依赖
- 测试用例间共享操作与数据(数据安全),节省资源
- 测试只关注对测试对象的输入与输出,如果无法做到这一点,就需要重构测试代码至符合这一点
- 注意多线程环境的测试安全
- 用户导向的面向对象软件设计
- 不修改待测对象的代码(但也提供自由度去修改)
- 所有操作接口化,用户只需要继承接口即可实现测试操作
- 通过增加运行参数/环境变量增加自由度
- 大多数测试相关类的生命周期由软件管理,不给用户添加负担
- 提供便利的输入方式(软件自带+谓词函数)
- 提供多样化可定制的输出信息
- 提供泛化层次,例如抽象测试类,参数化测试
- 特化常用需求,例如死亡测试
更丰富的错误信息输出
使用Bool函数+谓词断言
例子
1 | // 两数是否互为质数 |
失败的错误信息如下:
1 | MutuallyPrime(b, c) is false, where |
包装测试函数的返回值为AssertionResult
一般情况下可以配合谓词断言使用.
1 | namespace testing { |
::testing::AssertionResult
重载了<<
方便流式打印操作.
1 | testing::AssertionResult IsEven(int n) { |
EXPECT_TRUE(IsEven(Fib(4)))
输出
1 | Value of: IsEven(Fib(4)) |
一般模式
1 | bool IsEven(int n) { |
输出,不知道为什么错误.
1 | Value of: IsEven(Fib(4)) |
使用EXPECT_PRED_FORMAT*
例子如下:
1 | // 返回最小公约数 |
失败的case的格式化打印如下:
1 | b and c (4 and 10) are not mutually prime, as they have a common divisor 2 |
自定义打印格式
重载<<
操作符或者PrintTo()
函数,或者使用::testing::PrintToString(x)
,返回值是std::string
1 |
|
1 | vector<pair<Bar, int> > bar_ints = GetBarIntVector(); |
断言的放置Assertion Placement
非fatal的断言可以随意防止在项目代码里. fatal断言(FAIL*
and ASSERT_*
)必须放在返回void的函数里(原因是没使用异常).
但是有例外,构造函数与析构函数虽然也是返回void的但是不能放入fatal断言.
同时,需要注意放入构造函数/析构函数调用的private void-returning method有可能会导致构造/析构不完全.
跳过测试
使用GTEST_SKIP()
宏.
两种使用方式:
- Test Fixture 里的 SetUp 函数里,所有用到此 Test Fixture 的都会被跳过.
1
2
3
4
5
6
7
8
9
10
11class SkipFixture : public ::testing::Test {
protected:
void SetUp() override {
GTEST_SKIP() << "Skipping all tests for this fixture";
}
};
// Tests for SkipFixture won't be executed.
TEST_F(SkipFixture, SkipsOneTest) {
EXPECT_EQ(5, 7); // Won't fail
} ::testing::Environment
或::testing::Test
中直接使用1
2
3
4TEST(SkipTest, DoesSkip) {
GTEST_SKIP() << "Skipping single test";
EXPECT_EQ(0, 1); // Won't fail; it won't be executed
}
死亡测试
见之前的博文.
Set-Up and Tear-Down的层次
恰如test的三层架构:testsuites
-> testsuite
-> test
.
测试的资源共享也可以在这三个层面发生,想对应地为:global set-up and tear-down
(::testing::Environment
) ->static void SetUpTestSuite()
and static void TearDownTestSuite()
->void SetUp() override
and void TearDown() override
每个层级的 SetUp
都发生在整个层次的开始, 整个层次的最后必然会调用 TesrDown
清除数据.
Global Set-Up and Tear-Down
一个test program里全部都会共享的操作/数据可以定义在::testing::Environment
的自定义子类里.
自定义格式如下:
1 | class Environment : public ::testing::Environment { |
实例化的操作为:
1 | Environment* AddGlobalTestEnvironment(Environment* env); |
有几点要注意:
- 不要手动 delete
Environment
Google Test 会自动删除. - 在
RUN_ALL_TESTS()
之前把Environment
实例化. - 最好是在
main()
函数里依次初始化上一条的操作. - 如果执意使用
gtest_main
来省略main()
, 其中一种实例化Environment
的方法是将其定义为全局变量,如下:相应地你就必须要自己承担全局变量可能带来的风险:multiple environments from different translation units and the environments have dependencies among them (remember that the compiler doesn’t guarantee the order in which global variables from different translation units are initialized).1
2testing::Environment* const foo_env =
testing::AddGlobalTestEnvironment(new FooEnvironment);
Sharing Resources Between Tests in the Same Test Suite
基本形式与Test Fixture差不多,通过例子讲解:
1 | class FooTest : public testing::Test { |
除了注释里的注意点外还有一下点需要注意:
- 由于一个 Test Suite 里的
Test
的执行顺序是随机的,一定要注意对共享数据的顺序型的操作可能会导致未定义行为. - 对于共享的 static 数据注意进行内存管理, Google Test 没有提供支持.
- 当使用
TEST_P
参数化测试时, 注意把SetUpTestSuite()
与TearDownTestSuite()
声明为public
,而不是protected
.
参数化测试 Value-Parameterized Tests
试想如下场景:
- 如果我们想在不同的 command-line flags 下测试代码(最常见的情形是标定参数,其他还有程序的不同模式,例如节能模式,运动模式等).
- 如果我们想测试不同 interface 下程序/对象的表现(例如不同客户的接口不同).
- 如果我们想做 data-driven testing ,想看不同数据下程序的表现.
问题是如何把参数/数据传给 test , 总不能纯手工 copy-paste + swtich case 吧. Google Test 提供了接收参数进行测试的方法.
创建参数化测试类
有两种方式:
- 多重继承自:
testing::Test
以及testing::WithParamInterface<T>
(pure interface). - 继承封装好的类:
testing::TestWithParam<T>
T
为任何 copyable 类型. 如果是裸指针,用户需要自己负责管理生命周期.SetUp
与TesrDown
必须声明为 public.
用法说明:
- 类内部通过
GetParam()
成员函数调用传入的参数. - 可以传参数到已存的 fixture class.
- 设置
test suite
时,后缀_P
. - 可以通过让 test fixture 继承
testing::WithParamInterface<T>
的方式向 test fixture 中传入参数. TEST_P
如果没有INSTANTIATE_TEST_SUITE_P
会导致测试 suite 结果 FAIL .如果故意设置为空的话,可以使用GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FooTest);
特殊标记,不会报 FAIL 进行测试.
代码例子如下:
1 | class FooTest : |
传入参数的方法
通过INSTANTIATE_TEST_SUITE_P
宏接收参数.
参数生成器
如果我们有一系列的参数如何传入? 难道还要一个个 typing 进去? Google Test 考虑到常用的参数的可能组合形式, 提供了 test parameters generator
.
两个重载函数:
INSTANTIATE_TEST_SUITE_P(
InstantiationName
,
TestSuiteName
,
param_generator
)
INSTANTIATE_TEST_SUITE_P(
InstantiationName
,
TestSuiteName
,
param_generator
,
name_generator
)
InstantiationName
: 用TEST_P
定义的参数化测试类实例名.TestSuiteName
:test suite
的名字.param_generator
:生成器类型,如下表.name_generator
:谓词函数,例如后面的lambda函数,用于根据输入的参数自定义一个具体的test的名称,如果对官方默认的名称命名方式(参见例子)觉得不方便使用的话.
自定义test名称要求:非空,不重复 ASCII 码组成,不包含下划线.
第一种函数的例子:
1 | INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, |
INSTANTIATE_TEST_SUITE_P
应该放在最 global namespace 里.
生成的test名称分别如下:
1 | MeenyMinyMoe/FooTest.DoesBlah/0 for "meeny" |
Parameter Generator | 行为 |
---|---|
Range(begin, end [, step]) |
步进 {begin, begin+step, begin+step+step, ...} . 不包括 end . step 默认为 1. |
Values(v1, v2, ..., vN) |
组合为元组 {v1, v2, ..., vN} . |
ValuesIn(container) or ValuesIn(begin,end) |
C数组,STL容器,迭代器对象 |
Bool() |
{false, true} . |
Combine(g1, g2, ..., gN) |
对于给定的 n个 generators g1 , g2 , …, gN 进行笛卡尔乘积得到std::tuple . |
name_generator
:必须接受TestParamInfo<class ParamType>
入参,返回值为std::string
. 例子如下:
1 | class MyTestSuite : public testing::TestWithParam<int> {}; |
这些参数有啥用?可以使用--gtest_filter
过滤特定的测试用例.
抽象测试Abstract Tests
定义一个测试的接口,封装起来用于多个程序的测试,也就是所谓的测试驱动开发TDD.
实施思路如下:
- 在
.h
头文件里定义 test fixture class. - 在
.cpp
文件里定义TEST_P
,同时包含.h
头文件. - 在测试项目中,包含
.h
,使用INSTANTIATE_TEST_SUITE_P()
初始化参数化测试实例开展测试.
测试 private属性的代码
理论上对任何对象的测试(类,函数,一段程序)都应当当作黑盒测试,即从用户的角度测试.
但是调试阶段我们还是要深入对象内部或者根本没对象的代码段去测试.因此分为2种场景:
static 非成员函数, namespace 函数
- 方法1:
#include
整个.cc
file 到测试文件中*_test.cc
. - 方法2: 把这些函数放入
*-internal.h
内部测试专用的头文件,测试文件在 include 进去.
private/protected成员函数
- 方法1: 将 test fixture 声明为待测类的友元.但是主要即便 test fixture 是待测类的友元,不代表 test fixture 的子类(实际测试类)自动是待测类的友元.需要手动设置.
- 方法2: 应用
Pimpl
模式(参考),将待测类重构到*-internal.h
中. - 方法3: 将test suite 打桩到待测类中,如下.注意待测类与测试 suite 必须在同一个 namespace 下.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24FRIEND_TEST(TestSuiteName, TestName);
namespace my_namespace {
class Foo {
friend class FooTest;
FRIEND_TEST(FooTest, Bar);
FRIEND_TEST(FooTest, Baz);
... definition of the class Foo ...
};
} // namespace my_namespace
namespace my_namespace {
class FooTest : public testing::Test {
protected:
...
};
TEST_F(FooTest, Bar) { ... }
TEST_F(FooTest, Baz) { ... }
} // namespace my_namespace
以上的很多方法,例如
*-internal.h
必须与生产过程解偶,只用于测试.
捕获失败异常
Google Test 中不使用异常机制,那如何测试expected failure
?gtest/gtest-spi.h
包含如下宏:
EXPECT_FATAL_FAILURE(statement, substring);
EXPECT_NONFATAL_FAILURE(statement, substring);
EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substring);
EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substring);
运行时生成测试用例(Registering tests programmatically)
一般测试都是静态地测试,即确定输入,编译时固定好测试用例,运行测试时即可得到测试结果.那如果有测试运行时才能确定的测试用例怎么办? Google Test 提供了::testing::RegisterTest
方便 runtime 确定测试用例.
模型如下:
1 | template <typename Factory> |
其中:
Factory
是一个move-constructible
的生产Fixture
的函数对象.factory对象的 signature of the callable isFixture*()
.test_suite_name
是Fixture
对象的ID.
具体使用案例如下:
1 | class MyFixture : public testing::Test { |
get测试的信息(各层次)
如下可以获取正在执行的测试用例的信息.
1 | // Gets information about the currently running test. |
从 UnitTest
单例对象中使用 GetInstance()->current_test_info()
得到当前测试的 TestInfo
.
::testing::TestInfo
里包含了test_suite_name
, name
, type_param
, value_param
, file
, line
等一个测试用例的具体信息.
参见:TestInfo.
监听测试API
Google Test 提供了 event listener API, 可以用来监听如下过程:
- 整个测试program的开始与结束
- test suite 状态
- test method 状态
用途例子: - 替换默认的XML报告输出格式,甚至可以输出为GUI的格式.
- 设置资源泄漏的 checkpoints 等.
有2个 hanlder 函数:
testing::TestEventListener
: 纯虚类,interface类.testing::EmptyTestEventListener
: 提供了 empty 的函数实现.
处理 event 的 hanlder 函数接受的入参类型如下:-
UnitTest
reflects the state of the entire test program, -
TestSuite
has information about a test suite, which can contain one or more tests, -
TestInfo
contains the state of a test, and -
TestPartResult
represents the result of a test assertion.
对监听类的定义例子如下:
1 | class MinimalistPrinter : public testing::EmptyTestEventListener { |
main()
函数调用上述监听类
通过初始化一个监听类的容器,并 new 出一个监听类,添加进入即可.注意 new 后不需要手动 delete, 系统会自动释放资源.
1 | int main(int argc, char** argv) { |
对failure-raising event 的限制
failure-raising 指的是 EXPECT_*()
, ASSERT_*()
, FAIL()
,即有可能报错的断言.
- 不能在
OnTestPartResult()
生成failure 断言,否则会导致OnTestPartResult()
循环被调用. OnTestPartResult()
本身
控制测试程序执行选项
在编译得出的可执行文件后的选项.
选项 | 说明 |
---|---|
--help -h -? /? |
help 文档 |
--gtest_list_tests |
列出所有的测试用例(树状) |
--gtest_filter |
过滤用例,有通配符规则 |
--gtest_fail_fast |
遇到第一个 FAIL 就停止测试 |
--gtest_also_run_disabled_tests |
等同于GTEST_ALSO_RUN_DISABLED_TESTS 宏设置,不为0时执行disabled 测试(前缀DISABLED_ 的测试) |
--gtest_repeat |
设置重复测试次数 |
--gtest_shuffle |
等同于设置GTEST_SHUFFLE 为1,使得测试用例执行顺序随机 |
--gtest_random_seed=SEED |
设置随机测试的随机种子(不使用默认种子) |
--gtest_color |
等同于GTEST_COLOR ,值为yes /no 控制终端输出是否为彩色.TERM 可以设置为风格:xterm or xterm-color |
--gtest_brief=1 |
等同于GTEST_BRIEF 为1,选择只输出 FAIL 信息,过滤 SUCCESS |
--gtest_print_time=0 |
等同于GTEST_PRINT_TIME 为0,选择不输出时间信息 |
--gtest_print_utf8=0 |
等同于GTEST_PRINT_UTF8 为0,输出字符串不为UTF8格式 |
--gtest_output |
等同于GTEST_OUTPUT . "xml:path_to_output_file" :输出xml格式报告,"json:path_to_output_file" :输出json格式 |
disable用例
1 | // Tests that Foo does Abc. |
自动生成测试报告 xml / json
RecordProperty("key", value)
命令可以组成xml的标签与内容, value
是 string 或者 int .用法如下
1 | TEST_F(WidgetUsageTest, MinAndMaxWidgets) { |
输出格式如下
1 | ... |
注意:
TEST
外使用RecordProperty
需要加上::testing::Test::
.key
不能与name, status, time, classname, type_param, value_param
重复RecordProperty
在TETS
使用的化,生成的标签会是top-level XML element.
生成的xml报告结构
1 | <testsuites name="AllTests" ...> |
更具体的例子
1 |
|
<testsuites>
:整个测试项目.<testsuite>
:单个的测试suite.<testcase>
:一个测试case.tests
:各层级的测试数目failures
:各层级的失败数目time
:测试运行时间长度timestamp
:时间戳
生成 JSON 格式例子:
1 | { |
分布式执行测试
分布式执行测试sharding, 每台机器叫在 shard.
暂时不会用到这个技术.略过.
类型测试以及类型参数化测试
TYPED_TEST
以及TYPED_TEST_SUITE
测试类型.TYPED_TEST_P
以及TYPED_TEST_SUITE_P
测试参数化的类型.
可以测试数据的类型.略过,用时查阅.
子程序中使用断言(Using Assertions in Sub-routines)
Google 提供此功能,后续有需求再总结查阅.
Google Test的高阶使用