1. 前言
近期组里项目代码出于后期测试和长期维护的考虑,需要集成测试框架和用例补全,现有开源测试框架里面,google的test框架对于C++项目的支持度较高,且官方文档的完善性也高,便于上手,所以选用gtest这一套。
2. gtest环境搭建
下载地址:
https://github.com/google/googletest/
环境搭建:
Git下来的源码里面包含了googletest和googlemock的文件夹,如果是做简单的gtest学习可以直接cmake编译就可以直接使用。如果需要加入到自己的C++项目中,需要编写Cmake文件,生成gtest/gmock静态库,再将编写的测试用例链接库就可以使用。我当前使用的目录结构如下图:
其中src为项目的源码文件夹,test文件夹用于存放测试用例,thirdpart用于存放三方库(gtest源码放在这里面)用于编译链接使用。
从github上下载的源码包含很多可选文件及文件夹,我本人使用的gtest文件目录主要包含这些文件夹,省去了没有用到的部分
剩下的就是Cmake文件的编写,写了个简单的demo如下:
project(myproject)
cmake_minimum_required(VERSION 3.0)
# 配置gcc编译参数
set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-O0 -m64 -g -fPIC ${CMAKE_CXX_FLAGS}")
set(GOOGLETEST_VERSION 1.10.0)
set(LIBRARY_OUTPUT_PATH lib)
#定义一些编译宏
add_definitions(-DFOR_GTEST)
#编译选项开关
option(BUILD_GMOCK "Builds the googlemock subproject" ON)
option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON)
# 头文件包含的目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googlemock/include
${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googlemock/src
${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googletest/include
${CMAKE_CURRENT_SOURCE_DIR}/thirdpart/gtest/googletest/src
)
# 需要包含的编译子目录
if(BUILD_GMOCK)
add_subdirectory( thirdpart/gtest/googlemock )
else()
add_subdirectory( thirdpart/gtest/googletest )
endif()
add_subdirectory( thirdpart/csv-parse)
# 编译单元测试用例代码
add_executable(unittest
test/main/gmock_main.cc
test/unit_test/xxx-test.cc
)
target_link_libraries(unittest
-pthread
gtest
gmock
)
gtest运行参数
- 命令行参数:–gtest_output=“xml:report.xml”,可以把控制台内容输出转储为 XML 格式,其中report为存储的文件名,e.g.
./unittest --gtest_output="xml:report.xml"
- 命令行参数:–gtest_repeat=n,表示进行n次的重复测试,某些测试进行多次尝试后可能会结果不一致,可以使用重复测试功能进行测试;参数–gtest_break_on_failure可以支持自动调试,运行测试有时候会失败,但是在大多数时候会顺利通过。这是与内存损坏相关的问题的典型特点。如果多次运行测试,就能够提高发现失败的可能性,e.g.
./unittest --gtest_repeat=2 --gtest_break_on_failure
表示重复运行相同的测试用例两次,如果测试失败,会自动调用调试器。
- 命令行参数:运用–gtest_filter来过滤case。有时候我们并不需要运行所有测试,尤其是在修改的代码只影响某几个模块的情况下。为了支持运行一部分测试,Google 提供 --gtest_filter=,e.g.
./unittest --gtest_filter=* #执行所有测试
./unittest --gtest_filter=PoissonUdpClientTest* #执行PoissonUdpClientTest开头的测试
./unittest --gtest_filter=-PoissonUdpClientTest* #不执行PoissonUdpClientTest开头的测试
- 测试用例参数:利用DISABLED 关键词实现临时禁用测试
说明:只需在逻辑测试名或单元测试名前面加上 DISABLE_ 前缀,它就不会执行了,e.g.
TEST(DISABLE_FooTest, DoSomething)
- 命令行参数:–gtest_also_run_disabled_tests,对于DISABLE的用例依然执行,e.g.
./unittest –gtest_also_run_disabled_tests
3. 编写测试用例
TEST使用
常规的简单测试可以使用TEST宏,具体形式如下:
// TestName表示本次测试的名称,第二个参数TestDecription用作描述,执行时控制台显示 TestName.TestDescription可以看到用例执行结果
TEST(TestName, TestDescription)
{
…
}
宏展开后的实质内容为TEST测试类:
class TestName_ TestDescription_Test : public testing::Test {
...
void TestBody()
{
…
}
...
};
单元测试套件和TEST_F的使用
在单元测试中,我们经常需要在某个测试套件、测试用例或者整个测试运行之前进行前置条件设置及检查,或者运行之后对运行结果进行校验等操作,包括初始化操作和资源回收等,谷歌提供了这样一套测试套件,下面举例说明。
class FooTest : public ::testing::Test {
protected:
//Override this to define how to set up the environment.
virtual void SetUp()
{}
// Override this to define how to tear down the environment.
virtual void TearDown()
{}
// shared resource initialize
static void SetUpTestCase()
{}
// shared resource delete
static void TearDownTestCase()
{}
};
测试套件类经常需要使用到的接口主要是以上4个,需要继承::testing::Test
类,重写SetUp()
和TearDown()
两个虚方法,在每个测试套件用例执行前会执行SetUp()
的部分,在每个测试套件用例执行完之后会执行TearDown()
的部分,主要用于每个执行用例的前置初始化和环境清除。
在测试套件的第一个测试用例开始前,SetUpTestCase函数会被调用,而在测试套件中的最后一个测试用例运行结束后,TearDownTestCase函数会被调用。适用场景主要是:
1)初始化数据涉及内存申请等操作,为每个测试实例构造对象将带来较大系统开销;
2)存在某数据,其在每个实例中均被用到,但每个实例都不会更改该数据的值。
有了上面的测试套件,接下来就只需写TEST_F测试用例了,形式参考
// 第一个参数必须为测试套件类名,第二个参数为测试描述
TEST_F(FooTest, FuncA)
{
Foo foo;
foo.FuncA();
}
值参数化和TEST_P使用
在测试用例中,我们时常需要传给被测函数不同的值,gtest为我们提供了简便的方法,可以使我们能够灵活的进行参数化测试。
这里直接给出最通用化场景值参数化的使用步骤:
- 将需要使用的多参数封装成一个参数类,如
//Step1:申明一个呼叫参数类,该类主要用于TEST_P宏中实现的测试逻辑使用
// 把需要用到两个参数封装成一个参数类
class CallArgs {
public:
CallArgs(int argOne, double argTwo) :
m_argOne(argOne), m_argTwo(argTwo) {}
int GetArgOne()
{
return m_argOne;
}
double GetArgTwo()
{
return m_argTwo;
}
private:
int m_argOne;
double m_argTwo;
};
- 申明一个呼叫类,该类类名同时也是TEST_P宏的第一个参数test_case_name
// 该类继承了TestWithParam<CallArgs>模版类,从而使得CallArgs类与Call类进行了关联。
class Call: public ::testing::TestWithParam<CallArgs> {
};
- 使用INSTANTIATE_TEST_CASE_P宏,对Call类进行类相关多个的参数设置
// 这里只添加了两组参数,事实上,可以添加更多。
// 这里使用参数生成器::testing::Values,GTest定义了了很多参数生成器。
INSTANTIATE_TEST_CASE_P(VOIP, Call, ::testing::Values(
CallArgs(2, 9.6),
CallArgs(4, 14.5)
) );
- 编写了使用TEST_P宏实现的测试用例
// 使用了TestWithParam<CallArgs>类的GetParam()接口获取参数CallArgs
// 实际上这是两个测试用例,即该代码段会执行两组用例
TEST_P( Call, makeCall)
{
CallArgs args = GetParam();
ASSERT_TRUE(makeCall(args.GetArgOne(), args.GetArgTwo()));
}
protected/private方法测试
测试类的public方法,测试配置的输入一般为入参和成员变量的状态值,测试的输出一般为返回值和某些成员状态变化的值,但是类对象不能直接访问protected/private成员。
如果需要测试类的protected/private方法,我这里总结了3种方法:
-
首先一般不建议对非public成员函数直接访问,如果需要测试非public成员函数,可以被测试的类中新建一些public接口进行调用的方式;
-
可以在被测类的头文件中加入类似下面这种宏定义,将protected/private成员转为public访问类型
#ifdef FOR_GTEST
#define private public
#define protected public
#endif
然后在cmake文件中加入FOR_GTEST的编译宏,在测试代码中就可以直接访问调用
add_definitions(-DFOR_GTEST)
- 使用gtest框架的提供的FRIEND_TEST
在被测类的头文件需要引入包含该宏的头文件gtest/gtest_prod.h,然后添加宏进行友元声明,简单示例如下:
// foo.h
#include <gtest/gtest_prod.h>
// Defines FRIEND_TEST.
class Foo {
...
private:
FRIEND_TEST(FooTest, BarReturnsZeroOnNull);
int Bar(void* x);
};
// foo_test.cc
...
TEST(FooTest, BarReturnsZeroOnNull) {
Foo foo;
EXPECT_EQ(0, foo.Bar(NULL));
// Uses Foo's private member Bar().
}
gmock使用
gmock是谷歌推出的开源白盒测试工具,用于编写C++模拟类的框架。通过gmock可以用一些简单的宏描述想要模拟的接口并指定其期望,在测试中有效地去除外部依赖,更方便地测试模块功能。
对类里面需要打桩的函数mock,语法如下:
MOCK_METHODn(..., ...); //其中n表示参数的个数
MOCK_CONST_METHODn(..., ...); //const成员方法用这种
对mock的方法可以指定期望,包括返回值,调用次数等,使用EXPECT_CALL()宏:
EXPECT_CALL(mock_object, method(matchers))
.Times(cardinality) //可以指定调用几次
.WillOnce(action) //可以指定调用行为
.WillRepeatedly(action);
Matchers指参数匹配器,可以指定任意参数,::testing::_ 表示输入的参数为任意参数,其他参数不一一列举
Mock virtual方法如下,不需要对工程代码做修改:
// Foo.h
class Foo {
public:
virtual int FooFuncOne(int num);
};
// FooTest.cc
class MockFoo : public Foo {
public:
MOCK_METHOD1(FooFuncOne, int(int num));
};
TEST(FooTest, FooFuncOne)
{
MockFoo mockFoo;
// 指定FooFuncOne返回值为5
EXPECT_CALL(mockFoo, FooFuncOne((::testing::_)))
.WillRepeatedly((::testing::Return)(5));
}
上面mock方法主要用到了虚函数重写,但是对于非虚函数,上述写法是不能够生效的,需要有较大的改动,可以根据实际需要选择。下面贴了使用的示例:
// foo.h
class Foo {
public:
void CallSelfMethod();
void PublicMethod();
protected:
void ProtectedMethod();
private:
void PrivateMethod();
};
// 重构成模板类 foo_testable.h
template <typename T>
class FooTestable {
public:
FooTestable(T &self);
void CallSelfMethod();
void PublicMethod();
protected:
void ProtectedMethod();
private:
void PrivateMethod();
T &self;
};
// foo_test.cc
class MockFoo {
public:
MOCK_METHOD(PublicMethod, void());
MOCK_METHOD(ProtectedMethod, void());
MOCK_METHOD(PrivateMethod, void());
};
TEST(Test_MockSelfNonVirtualMethod, SelfMethod) {
MockFoo mockFoo;
FooTestable<MockFoo> fooTestable(mockFoo);
EXPECT_CALL(mockFoo, PublicMethod()).Times(1);
EXPECT_CALL(mockFoo, ProtectedMethod()).Times(1);
EXPECT_CALL(mockFoo, PrivateMethod()).Times(1);
fooTestable.CallSelfMethod();
}
4. 常用断言宏
5. 参考资料
- gmock官方入门/进阶文档
googlemock/docs/ForDummies.md
googlemock/docs/CookBook.md - 博客
mock非虚函数