【单元测试】Google Test(GTest)和Google Mock(GMock)--编辑中
目录
进阶:测试我们函数的API--ASSERT_*和EXPECT_*
作者:bandaoyu 原文:https://blog.csdn.net/bandaoyu/article/details/124374057?spm=1001.2014.3001.5501
Gtest简介
GoogleTest 是 Google 的 C++ 测试和模拟框架,是库,提供了一些API接口,用于测试你的程序。
我们编写测试文件,里面调用GoogleTest的API 测试我们的函数。然后编译的时候把GoogleTest的库链接进来即可。
Gtest是google开发的一个开源的C++测试框架,可在Linux, Windows,Mac多个平台上对C++源码进行测试,不仅支持单元测试,也支持其他类型测试。
一 基本概念
使用gtest时,就是编写断言(assertions),断言语句会检测条件是否为真。一个断言可存在三种结果:success(成功),nonfatal failure(非致命失败),或 fatal failure(致命失败)。当出现致命失败时,终止当前函数;否则程序继续执行。
Tests使用断言语句来检测代码的结果。如果一个test出现崩溃或有一个失败的断言,则该test是fails,否则是succeeds。
一个test suite包括一个或多个tests。可以将多个tests分组到test suite中,以此反映所测试代码的结构。当一个test suite中的多个tests需要共享一些通用对象和子程序时,可将其放入一个test fixture class。
一个test program可包含多个test suites.
局限性
gtest 是线程安全的,但是这个线程安全仅仅在支持 pthread 的系统的可以。在其他系统中使用两个线程运行测试是不安全的,比如 windows。
入门例子
编译环境中安装Gtest,既编译Gtest的源码编出Gtest的库gtest,放到链接目录下(供后面测试代码链接它和调用它的API)
$ git clone https:
//github
.com
/google/googletest
.git
$
cd
googletest
$
mkdir
build
$
cd
build
$ cmake ..
$
make
$
sudo
make
install
我们的工程文件(程序/函数)
mySrc.h
int
Foo(
int
a,
int
b);
mySrc.cpp
int
Foo(
int
a,
int
b)
{
if
(0 == a||0 == b)
throw
"don't do that"
;
int
c = a%b;
if
(0 == c)
{
return
b;
}
return
Foo(b,c);
}
编写单元测试工程(文件)
test.cpp
里面调用Gtest的API测试我们的函数/程序:
#include "
mySrc.h"
#include <gtest/gtest.h>
#TEST 就是Gtest的API,用于测试我们的程序的函数FooTest等
TEST(FooTest,HandleNoneZeroInput)
{
EXPECT_EQ(2,Foo(4,10));
EXPECT_EQ(6,Foo(30,18));
}
int
main(
int
argc,
char
*argv[])
{
testing::InitGoogleTest(&argc,argv);
return
RUN_ALL_TESTS();
}
编译单元测试工程
$ g++ -std=c++11
test.cpp
mySrc.cpp mySrc.h-lgtest -lpthread ./a.out
执行单元测试
./a.out
摘自:Gtest学习系列一:Gtest安装与基本测试 :https://www.cnblogs.com/yanqingyang/p/12732087.html
还可以打印信息
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
进阶:测试我们函数的API--ASSERT_*和EXPECT_*
TEST, TEST_F TEST_P的区别
TEST()
Example test
适合给static或全局函数或简单类编写单元测试时.
TEST_F()
. Example test
测试夹具(Test Fixtures):对多个测试使用相同的数据配置。多个测试来操作类似的数据,你可以使用测试夹具。它允许您为几个不同的测试重复使用相同的对象配置。
可以编写默认构造函数或SetUp()函数来为几个测试准备(共同)对象。
如果需要,写一个析构函数或TearDown()函数来释放你在SetUp()中分配的任何资源。
更多详情见:Google C++单元测试框架GoogleTest---TestFixture使用 - 超超boy - 博客园
TEST_P()
TEST_P()
当您想使用参数编写测试时,TEST_P()非常有用。您可以使用test_P()编写一个测试,而不是使用不同的参数值编写多个测试,test_P()使用GetParam()并可以使用INSTANTIATE_test_SUITE_P()进行实例化。示例测试. Example test
googletest - What is the difference between TEST, TEST_F and TEST_P? - Stack Overflow
TEST_F与TEST的区别是,TEST_F提供了一个初始化函数(SetUp)和一个清理函数(TearDown),在TEST_F中使用的变量可以在初始化函数SetUp中初始化,在TearDown中销毁,并且所有的TEST_F是互相独立的,都是在初始化以后的状态开始运行,一个TEST_F不会影响另一个TEST_F所使用的数据,下面是一个例子。
//A.h
#ifndef A_H
#define A_H
class A
{
private:
int _a;
public:
A( int a );
~A( );
void add( int a );
int getA( );
};
#endif
A.cpp
#include "A.h"
A::A( int a ){
this->_a = a;
}
A::~A( ){
}
void A::add( int a ){
this->_a += a;
}
int A::getA( ){
return this->_a;
}
#-----------------------------------------
A_test.cpp
#include "A.h"
#include <gtest/gtest.h>
class A_test : public testing::Test
{
protected:
A *_p_a;
virtual void SetUp( )
{
//初始化函数
this->_p_a = new A( 1 );
}
virtual void TearDown( )
{
//清理函数
delete this->_p_a;
}
};
//第一个测试,参数A_test是上面的那个类,第二个参数FirstAdd是测试名称
TEST_F( A_test, FirstAdd )
{
EXPECT_EQ( 1, _p_a->getA( ) );
_p_a->add( 3 );
EXPECT_EQ( 4, _p_a->getA( ) );
}
//第二个测试
TEST_F( A_test, SecondAdd )
{
EXPECT_EQ( 1, _p_a->getA( ) );
_p_a->add( 5 );
EXPECT_EQ( 6, _p_a->getA( ) );
}
/*
上面的两个测试都是在SetUp函数执行后的状态下执行,也就是说在执行任意一个TEST_F时 _p_a->_a 的值都是初始值1
*/
#-----------------------------------------
main.cpp
#include <gtest/gtest.h >
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#-----------------------------------------
编译后执行的结果如下:
$ gtester main
TEST:
main... (pid = 13965)
[ == == == == == ] Running 2 tests from 1 test case.
[----------] Global test environment set - up.
[----------] 2 tests from A_test
[ RUN ] A_test.FirstAdd
[ OK ] A_test.FirstAdd
[ RUN ] A_test.SecondAdd
[ OK ] A_test.SecondAdd
[----------] Global test environment tear - down
[ == == == == == ] 2 tests from 1 test case ran.
[ PASSED ] 2 tests.
PASS:
main
ASSERT_*和EXPECT_* 说明
不同点是:
ASSERT_*
在断言失败时产生致命错误,并且终止当前函数。
EXPECT_*
版本则产生非致命错误,且不会终止当前函数。
通常更倾向于使用EXPECT_*
,因为这样能够允许在一个测试用例中报告多个错误。
如果断言继续下去没有意义的话,就应该使用ASSERT_*
进行判断。
ASSERT_*
立刻从当前函数返回,可能会跳过之后的清理代码,这将会导致空间泄漏。根据泄漏的性质,它可能值得修复,也可能不值得修复。因此,如果除了断言错误外还出现堆检查器错误,请记住检查这一点。
基本的断言
判断真假的断言
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
比较断言
这类断言用来比较两个值
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 |
断言参数的值必须是可比较的,否则会产生一个编译错误。当断言失败时,如果自定义的错误支持<<
运算符,那么GTEST将会打印他们,否则将会尝试用其他的方式打印出他们。
用户自定义类型仅仅当定义了比较操作时,断言才能够比较的对象的大小,但是这不被Google的C++类型规范所提倡,这种情况下应当使用ASSERT_TRUE()
或者EXPECT_TRUE()
来进行判断。不过还是应当尽可能的使用ASSERT_EQ(actual, expected)
,因为他能够在测试失败时告知actual
和expected
的值。
ASSERT_EQ()
在比较指针时比较的是指针的值,当比较两个C风格的字符串时,将会比较他们是否有相同的内存地址,而不是有相同的值。因此在比较C风格字符串的时候应当使用ASSERT_STREQ()
,但是在比较两个string对象的时候,应当使用ASSERT_EQ
。
在进行指针的比较时应当使用*_EQ(ptr, nullptr)
和*_NE(ptr, nullptr)
代替*_EQ(ptr, NULL)
和*_NE(ptr, NULL)
,因为nullptr
被定义了类型而NULL
却没有。
当比较浮点数时应该使用浮点数断言来避免近似值导致的问题。
本节的宏定义对string
和wstring
都适用。
ps:2016年二月前的GTEST版本对*_EQ
断言有着ASSERT_EQ(expected, actual)
这样的顺序要求,但是新的*_EQ
对两个参数顺序没有要求。
字符串比较
这节的断言用来比较C语言风格的字符串,在比较两个string对象时,应该使用EXPECT_EQ
,EXPECT_NE
。
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,str2); | the two C strings have the same content |
ASSERT_STRNE(str1,str2); | EXPECT_STRNE(str1,str2); | the two C strings have different contents |
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 contents, ignoring case |
注意:“CASE”表明忽略大小写,一个NULL
指针和空字符串不一样
简单的测试例子
创建一个测试:
- 使用
TEST()
宏定义来定义和命名一个测试函数,这些宏就是没有返回值的普通C++函数。 - 在这个函数中,可以包含任何有效的c++语句中,使用各种GTEST断言来检查值。
- 测试结果由断言决定;如果测试中的任何断言失败(致命或非致命),或者测试崩溃,则整个测试失败。
TEST(TestSuiteName, TestName) {
... test body ...
}
TEST()
的第一个参数是测试套件(Test Suite)的名称,第二个参数是这个测试套件中该测试(Test)的名称。两种名称都必须是合法的C++标识符,并且不能包含任何下划线_
。不同测试套件中的测试可以有相同的名字。
举个例子,被测函数是一个简单的斐波那契函数:
int Factorial(int n); // Returns the factorial of n
一个测试可以写成:
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
逻辑上来说,相关的测试应该在同一个测试套件(Test Suite)中。在上述的例子中,有两个测试HandlesZeroInput
和HandlesPositiveInput
,他们属于同一个测试套件FactorialTest
。
Test Fixtures(为多个测试使用相同的配置)
当两个或更多的测试需要使用相似的数据时,可以使用Test Fixture。这可以对不同的测试重用相同的数据对象配置。
创建一个fixture:
- 从
::testing::Test
派生出一个类。用protected:
开始它的类主体,因为需要从子类访问fixture成员。 - 在类中声明所有准备使用的对象
- 如果需要,可以编写一个默认构造函数或
SetUp()
函数来为每个测试准备对象。常见的错误是将SetUp()
拼写为Setup()
,在c++ 11中可以使用override
来确保拼写正确。 - 如有必要,编写一个析构函数或
TearDown()
函数以释放您在SetUp()
中分配的所有资源。 若要了解何时应使用构造函数/析构函数以及何时应使用SetUp()/ TearDown()
,请阅读[FAQ][www.baidu.com]。 - 如果需要,定义要共享的测试的子程序。
当使用fixture时,使用TEST_F()
代替TEST()
,因为TEST_F()
允许你在Test Fixture中获取对象和子程序:
TEST_F(TestFixtureName, TestName) {
... test body ...
}
和TEST()
类似,第一个参数是测试套件的名字,但是TEST_F()
的这个参数必须和Test Fixture
类的名字相同。还需要在使用Test Fixture
对象之前定义这个Test Fixture
类,否则会导致编译错误virtual outside class declaration
。
对于每个TEST_F()
来说,GTEST在运行时都会创建一个新的test fixture对象,并且通过SetUp()
立刻初始化这个对象,再运行测试,结束后通过调用TearDown()
来进行清理工作,最后将删除这个test fixture对象。注意,在同一个测试套件中的不同测试拥有不同的test fixture对象,GTEST在新建下一个test fixture对象时总是会先删除上一个test fixture对象,并且不会在多个不同的测试中重用一个test fixture对象。所以如果任何测试改变了它的test fixture对象,并不会影响其他测试的test fixture对象。
下面用对一个FIFO队列类Queue
编写测试,他有以下接口:
template <typename E> // E is the element type.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Returns NULL if the queue is empty.
size_t size() const;
...
};
定义一个fixture类。按照惯例,应该给它起一个FooTest
的名字,其中Foo
是被测试的类。
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
在这个例子中,不需要TearDown()
函数,因为析构器已经完成了析构工作,不需要再进行清理。
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}
上面使用了ASSERT_*
和EXPECT_*
断言。当希望测试在断言失败后继续显示更多错误时使用EXPECT_*
,而在失败后继续运行测试没有意义则使用ASSERT_*
。例如,Dequeue测试中的第二个断言是ASSERT_NE(nullptr, n)
,因为我们稍后需要对指针n
进行解引用,这将在n
的值为NULL
时导致段错误。
当测试运行时,以下步骤将会发生:
- GTEST构建一个
QueueTest
对象t1
t1.SetUp()
初始化t1
- 第一个测试在
t1
上运行 t1.TearDown()
在第一个测试结束时进行清理- 析构
t1
- 在进行另外一个
QueueTest
对象测试DequeueWorks
测试时,重复上述步骤
调用测试
TEST()
和TEST_F()
向googletest隐式注册其测试。与许多其他C ++测试框架不同,不必重新列出所有已定义的测试即可运行它们。
定义测试后,可以使用RUN_ALL_TESTS()
运行它们,如果所有测试成功,将返回0,否则返回1。RUN_ALL_TESTS()
在链接单元中运行所有测试,它们可以来自不同的测试套件,甚至来自不同的源文件。
当调用RUN_ALL_TESTS()
宏时:
- 保存所有GTEST标志的状态
- 为第一个测试创建一个test fixture对象
- 通过
SetUp()
初始化这个对象 - 在fixture对象上运行测试
- 通过
TearDown()
函数进行清理 - 删除fixture对象
- 恢复所有GTEST标志的状态
- 重复上述步骤直到测试结束
当一个致命性的错误发生时,后续的步骤将会被跳过。
重要说明:一定不能忽略
RUN_ALL_TESTS()
的返回值,否则会出现编译器错误。 这种设计的基本原理是,自动化测试服务将根据其退出代码(而不是根据其stdout / stderr输出)来确定测试是否通过。 因此main()
函数必须返回RUN_ALL_TESTS()
的值。另外,您应该只调用一次
RUN_ALL_TESTS()
。 多次调用它会与某些高级googletest功能(例如线程安全的死亡测试)发生冲突,因此不被支持。
编写main()函数
gtest_main
库提供了一个合适的程序入口点,通过链接gtest_main
动态库而不是gtest
库,大多用户无需编写他们自己的main函数(Google Test提供了main()
函数的基本实现。如果适合你的需求,则只需将测试与gtest_main库链接就可以了。)。本节的其余部分仅适用于需要在测试运行前做一些自定义的事情,而这些事情不能在test fixture和测试套件的框架内表达。
如果您编写自己的main()
函数,则该函数应返回RUN_ALL_TESTS()
的值。
下面是一个模板:
#include "this/package/foo.h"
#include "gtest/gtest.h"
namespace my {
namespace project {
namespace {
// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
protected:
// You can remove any or all of the following functions if their bodies would
// be empty.
FooTest() {
// You can do set-up work for each test here.
}
~FooTest() override {
// You can do clean-up work that doesn't throw exceptions here.
}
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
void SetUp() override {
// Code here will be called immediately after the constructor (right
// before each test).
}
void TearDown() override {
// Code here will be called immediately after each test (right
// before the destructor).
}
// Class members declared here can be used by all tests in the test suite
// for Foo.
};
// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
const std::string input_filepath = "this/package/testdata/myinputfile.dat";
const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}
// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
}
} // namespace
} // namespace project
} // namespace my
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
:: testing :: InitGoogleTest()
函数解析命令行中的googletest标志,并删除所有可识别的标志。这允许用户通过各种标志控制测试程序的行为,将在AdvancedGuide中介绍这些标志。 注意,必须在调用RUN_ALL_TESTS()
之前调用该函数,否则标志将无法正确初始化。
已知的限制
GTEST被设计成线程安全的。在使用pthread
的系统上,GTEST的实现是线程安全的,而在其他系统(如Windows)上多线程并发使用Google Test
的断言并不安全。一般情况下断言都是在主线程中进行的,因此在绝大多数测试中这并不会产生问题。
C++测试框架--GTEST
作者:愿以光散黑
链接:https://www.jianshu.com/p/c7c702c0abb9
GMock简介
gmock是一款开源的白盒测试工具,测试一个模块的时候,可能涉及到和其他模块交互,可以将模块之间的接口mock起来,模拟交互过程。例如:
下面简单的说说打桩在白盒测试中的重要性:
1、比如银行业务,需要测试业务模块。此时,不可能去操作真实的数据库,并且搭建新的数据库可能比较复杂或者耗时。那么就可以用gmock将数据库接口地方打桩,来模拟数据库操作。
2、比如要测试A模块,必过A模块需要调用B模块的函数。如果B模块还没有实现,此时,就可以用gmock将B模块的某些接口打桩。这样就可以让A模块的测试继续进行下去。
3、比如网关设备,在用gtest测试device模块的时候,必须有真实的设备才能让测试进行下去。如果用gmock模拟一套sdk接口,那么无需真实的设备也能让测试进行下去。(本条仅限公司内部阅读)
使用方法示例
我们工程有一个类Cdd2是这样的
class Cdd2
{
public:
Cdd2() {}
virtual ~Cdd2() {}
virtual std::string getAttrString() = 0;
virtual int getPosition(int parm) = 0;
};
而后咱们须要定义个 Mock 类来继承我们要mock的类Cdd2,而且定义须要模拟(mock)的方法:getAttrString, getPosition。这里咱们用到了宏定义MOCK_METHOD0,MOCK_METHOD1后面的数字表明了模拟函数的参数个数,如MOCK_METHOD0,MOCK_METHOD1。它接受两个参数:
头文件中还有其他类似宏定义,如MOCK_METHOD0,MOCK_METHOD2...
MOCK_METHOD#1(#2, #3(#4) )
#2是你要mock的方法名称!#1表示你要mock的方法共有几个参数,#4是这个方法具体的参数,#3表示这个方法的返回值类型。很简单不是?!
class MockCdd2:public Cdd2
{
public:
//0和1代表了参数的个数
MOCK_METHOD0(getAttrString,std::string());
MOCK_METHOD1(getPosition,int(int));
};
经过这个宏定义,咱们已经初步模拟出对应的方法了。接下来咱们在TEST里告诉 Mock Object 被调用时该如何动作(就是给测试模拟什么样的输出):
TEST(MockTestCase, Demo1)
{
int n = 100;
std::string value = "Hello World!";
MockCdd2 mockFoo;//期待运行1次,且返回值为value的字符串<--就是告诉测试,调到getAttrString方法就模拟返回value
EXPECT_CALL(mockFoo, getAttrString())
.Times(1)
.WillOnce(testing::Return(value));
std::string returnValue = mockFoo.getAttrString();
std::cout << "Returned Value: " << returnValue << std::endl;//期待运行两次,返回值分别为335 和 455<--就是告诉测试,调到getPosition方法就模拟第一次返回334,第二次返回455
EXPECT_CALL(mockFoo, getPosition(testing::_))
.Times(2)
.WillOnce(testing::Return(335))
.WillOnce(testing::Return(455));int val = mockFoo.getPosition(0); //355
int val2 = mockFoo.getPosition(1); //455
std::cout << "Returned Value: " << val << " " << val2 << std::endl;
}
运行结果:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MockTestCase
[ RUN ] MockTestCase.Demo1
Returned Value: Hello World!
Returned Value: 335 455
[ OK ] MockTestCase.Demo1 (17 ms)
[----------] 1 test from MockTestCase (19 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (28 ms total)
[ PASSED ] 1 test.
说明:虽然MockCdd2 继承于 Cdd2,但是其实并没有执行Cdd2的相关函数,而是MockCdd2的期望返回值,我们通过手动设置返回值的方式,达到了测试打桩的目的.
更多例子:
C++雾中风景番外篇2:Gtest 与 Gmock:http://www.javashuo.com/article/p-xxpzzetu-gd.html
Gmock使用说明:https://www.cnblogs.com/huaibin/p/15400562.html
google mock是用来配合google test对C++项目做单元测试的。它依赖于googletest
1. Makefile里面需要加入 -lgmock才能正常连接
AM_LDFLAGS=-lpthread -lc -lm -lrt -lgtest -lgmock
3. 一个类中,只有virtual的member funciton能被mock(试验得到的结论),调用被mock的member function,function行为变成mocked behavior,调用类中没有被mock的member function,function行为与原类相同,不被mock改变。
ps,写了一个简单类,不是virtual居然也能被mock,奇怪。。。。在一个复杂类中,必须是virtual的才能被mock。 这些是实验得到的结论。
从理论上分析,应该必须是virtual才可以。所以,确定哪些函数要被mock,然后在头文件中将其virtual化。不然可别说我没有预先告诉你哦;)
4. 一个被Mock的函数,如果没有在EXPECT_CALL中指定expected behavior,系统将会为其指派默认行为(什么都不做,返回0),并且在屏幕上打印WARNING:
GMOCK WARNING:
Uninteresting mock function call - returning default value.
Function call: get_next_row(@0x7fff51a6b888 0x30c51529e0)
Returns: 0
Stack trace:
摘抄自:https://blog.csdn.net/maray/article/details/7750617
好文备份
Gmock使用说明
Gmock是C++中的一个接口测试框架,一般来说和Google Test搭配使用,但Google Test也可以和其他Mock框架一起使用。 本部分是Google Mock基础常用的用法,如需要特殊用法,请查阅Google Mock官方文档。
一、安装部署
依次执行下面命令即可:
git clone https://github.com/google/googletest
git checkout release-1.8.0
cd ~/googletest && cmake .
make && sudo make install
官方文档:GoogleTest User’s Guide | GoogleTest
二、用法说明
-
Fake、Mock、Stub
- Fake对象有具体的实现,但采取一些捷径,比如用内存替代真实的数据库读取
- Stub对象没有具体的实现,只是返回提前准备好的数据
- Mock对象和Stub类似,只是在测试中需要调用时,针对某种输入指定期望的行为,Mock和Stub的区别是,Mock除了返回数据还可以指定期望以验证行为。
-
简单示例
Tutle类:
class Turtle {
...
virtual ~Turtle() {};
virtual void PenUp() = 0;
virtual void PenDown() = 0;
virtual void Forward(int distance) = 0;
virtual void Turn(int degrees) = 0;
virtual void GoTo(int x, int y) = 0;
virtual int GetX() const = 0;
virtual int GetY() const = 0;
};
MockTurtle类:
#include "gmock/gmock.h"
class MockTurtle : public Turtle {
public:
...
MOCK_METHOD(void, PenUp, (), (override));
MOCK_METHOD(void, PenDown, (), (override));
MOCK_METHOD(void, Forward, (int distance), (override));
MOCK_METHOD(void, Turn, (int degrees), (override));
MOCK_METHOD(void, GoTo, (int x, int y), (override));
MOCK_METHOD(int, GetX, (), (const, override));
MOCK_METHOD(int, GetY, (), (const, override));
};
-
创建Mock类的步骤:
-
MockTutle继承Tutle
-
找到Tutle的一个虚函数
-
在public的部分,写一个MOCK_METHOD()
-
将虚函数的函数签名复制进MOCK_METHOD()中,加两个逗号:
一个在返回类型和函数名之间,另一个在函数名和参数列表之间
例如:void PenDown() 有三部分:void、PenDown、和(),这三部分就是MOCK_METHOD的前三个参数
-
如果要模拟const方法,添加一个包含const的第四个参数,必须到括号
-
建议添加override关键字。所以对于const方法,第四个参数变为(const, override),对于非const方法,第四个参数变为override。这不是强制性的。
-
重复步骤直至完成要模拟的所有虚拟函数
-
-
在测试中使用Mock
在测试中使用Mock的步骤:
- 从testing名称空间导入gmock.h的函数名(每个文件只需要执行一次)
- 创建一些Mock对象
- 指定对它们的期望(方法将被调用多少次? 带有什么参数? 每次应该做什么? 返回什么值 等等)
- 使用Mock对象;可以使用googletest断言检查结果。如果mock函数的调用超出预期或参数错误,将会立即收到错误信息。
- 当Mock对象被销毁时,gmock自动检查对模拟的所有期望是否得到满足
#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"using ::testing::AtLeast; // #1
TEST(PainterTest, CanDrawSomething) {
MockTurtle turtle; // #2
EXPECT_CALL(turtle, PenDown()) // #3
.Times(AtLeast(1));Painter painter(&turtle); // #4
EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); // #5
}
-
在这个例子中,我们期望tutle的PenDown()至少被调用一次。如果在tutle对象被销毁时,PenDown()还没有被调用或者调用两次以上,测试会失败。
-
指定期望
EXCEPT_CALL(指定期望)是使用Google Mock的核心。EXCEPT_CALL的作用是两方面的:
-
告诉这个Mock(假)方法如何模拟原始方法:
我们在EXPECT_CALL中告诉Google Mock,某个对象的某个方法第一次被调用时,会修改某个参数,会返回某个值,第二次调用时, 会修改某个参数,会返回某个值......
-
验证被调用的情况
我们在EXPECT_CALL中告诉Google Mock,某个对象的某个方法总共会被调用N次(或大于N次,小于N次)。如果
最终次数不符合预期,会导致测试失败。
4.1 基本语法
-
EXPECT_CALL(mock_object, method(matchers))
.Times(cardinality)
.WillOnce(action)
.WillRepeatedly(action);
- mock_object是对象
- method(matchers)用于匹配相应的函数调用
- cardinality指定基数(被调用次数情况)
- action指定被调用时的行为
例子:
using ::testing::Return;
...
EXPECT_CALL(turtle, GetX())
.Times(5)
.WillOnce(Return(100))
.WillOnce(Return(150))
.WillRepeatedly(Return(200));
这个EXPECT_CALL()指定的期望是:在turtle这个Mock对象销毁之前,turtle的getX()函数会被调用五次。第一次返回100,第二次返回150,第三次及以后都返回200。指定期望后, 5次对getX的调用会有这些行为。但如果最终调用次数不为5次,则测试失败。
4.2 参数匹配:哪次调用
using ::testing::_;
using ::testing::Ge;
// 只与Forward(100)匹配
EXPECT_CALL(turtle, Forward(100));
// 与GoTo(x,y)匹配, 只要x>=50
EXPECT_CALL(turtle, GoTo(Ge(50), _));
- _ 相当于“任何”
- 100相当于Eq(100)
- Ge(50)指参数大于或等于50
- 如果不关心参数,只写函数名就可以。比如:EXPECT_CALL(turtle, GoTo)
4.3 基数:被调用几次
用Times(m),TIme(AtLeast(n))等来指定期待的调用次数
Times可以被省略。比如整个EXPECT_CALL只有一个WillOnce(action)相当于也说明了调用次数只能为1
4.4 行为:该做什么
常用模式:如果需要指定前几次调用的特殊情况,并且之后的调用情况相同。使用一系列WillOnce()之后有WillRepeatedly()
除了用来指定调用返回值的Return(),Google Mock中常用行为中还有:SetArgPointee(value),SetArgPointee将第N个指针参数(从0开始)指向的变量赋值为value。
比如void getObject(Object* response){...}的EXCEPT_CALL:
Object* a = new Object;
EXPECT_CALL(object, request)
.WillOnce(SetArgPointee<1>(*a));
就修改了传入的指针response,使其指向了一个我们新创建的对象 。
如果有多个行为,应该使用DoALL(a1, a2, ..., an)。DoAll指向所有n个action并返回an的结果。
4.5 使用多个预期
例子:
using ::testing::_;
...
EXPECT_CALL(turtle, Forward(_)) // #1
.Times(3);
EXPECT_CALL(turtle, Forward(10)) // #2
.Times(2);
...mock对象函数被调用...
//Forward(10); // 与#2匹配
//Forward(20); // 与#1匹配
正常情况下,Google Mock以倒序搜索预期:如果和多个EXCEPT_CALL都可以匹配,只有之前的,距离调用最近的一个EXPECT_CALL()会被匹配。例如:
- 连续三次调用Forward(10)会产生错误因为它和 #2 匹配
- 连续三次调用Forward(20)不会有错误因为它和 #1 匹配
一旦匹配,该预期会被一直绑定,即使执行次数达到上限之后,还是生效的,这就是为什么三次调用Forward(10)超过了2号的EXPECT_CALL的上限时,不会去试图调用绑定1号EXPECT_CALL而报错的原因。
为了明确地让某一个EXPECT_CALL “退休”, 可以加上RetiresOnSaturation(),例如:
using ::testing::Return;
EXPECT_CALL(turtle, GetX()) // #1
.WillOnce(Return(10))
.RetiresOnSaturation();
EXPECT_CALL(turtle, GetX()) // #2
.WillOnce(Return(20))
.RetiresOnSaturation();turtle.GetX() // 与#2匹配,返回20,然后#2“退休”
turtle.GetX() // 与#1匹配,返回10
在这个例子中,第一次GetX()调用和#2匹配,返回20,然后这个EXPECT_CALL就 “退休”了;第二次 GetX()调用和 #1匹配,返回10
4.6 Sequence
可以用sequence来指定期望匹配的顺序
using ::testing::Return;
using ::testing::Sequence;
Sequence s1, s2;
...
EXPECT_CALL(foo, Reset())
.InSequence(s1, s2)
.WillOnce(Return(true));
EXPECT_CALL(foo, GetSize())
.InSequence(s1)
.WillOnce(Return(1));
EXPECT_CALL(foo, Describe(A<const char*>()))
.InSequence(s2)
.WillOnce(Return("dummy"));
在上面的例子中,创建了两个Sequence s1 和 s2,属于 s1 的有Reset() 和 GetSize(),所以Reset()必须在GetSize()之前执行。属于s2的有Reset()和Describe(A<const char*>()),所以Reset()必须在Describe(A<const char >())之前执行。所以,Reset()必须在Describe(A<const char>())之前执行,而GetSize()和Describe()这两者之间没有顺序约束。
如果需要指定很多期望的顺序,有另一种写法:
using ::testing::InSequence;
{
InSequence seq;EXPECT_CALL(...)...;
EXPECT_CALL(...)...;
...
EXPECT_CALL(...)...;
}
在这种用法中,scope(大括号中)的期望必须遵守严格的顺序。
三、情景示例
在这部分,我们找一个示例项目来演示,如何在不同的情景中使用Google Test和 Google Mock写单元测试用例。
项目结构
示例项目是一个C++命令行聊天室软件,包含服务器和客户端。
.
├── CMakeLists.txt
├── README.md
├── client_main.cpp
├── server_main.cpp
├── include
│ ├── chat_client.hpp
│ ├── chat_message.hpp
│ ├── chat_participant.hpp
│ ├── chat_room.hpp
│ ├── chat_server.hpp
│ ├── chat_session.hpp
│ ├── http_request.hpp
│ ├── http_request_impl.hpp
│ ├── message_dao.hpp
│ └── message_dao_impl.hpp
├── src
│ ├── chat_client.cpp
│ ├── chat_message.cpp
│ ├── chat_room.cpp
│ ├── chat_server.cpp
│ ├── chat_session.cpp
│ ├── http_request_impl.cpp
│ └── message_dao_impl.cpp
└── tests
├── chat_message_unittest.cpp
└── chat_room_unittest.cpp
普通测试
如果被测试的函数不包含外部依赖,用Google Test基础的用法就可以完成用例编写。
原函数:
void chat_message::body_length(std::size_t new_length) {
body_length_ = new_length;
if (body_length_ > 512)
body_length_ = 512;
}
这个函数很简单,就是给body_length_赋值,但是有最大值限制。测试用例可以这样写:
TEST(ChatMessageTest, BodyLengthNegative) {
chat_message c;
c.body_length(-50);
EXPECT_EQ(512, c.body_length());
}TEST(ChatMessageTest, BodyLength0) {
chat_message c;
c.body_length(0);
EXPECT_EQ(0, c.body_length());
}TEST(ChatMessageTest, BodyLength100) {
chat_message c;
c.body_length(100);
EXPECT_EQ(100, c.body_length());
}TEST(ChatMessageTest, BodyLength512) {
chat_message c;
c.body_length(512);
EXPECT_EQ(512, c.body_length());
}TEST(ChatMessageTest, BodyLength513) {
chat_message c;
c.body_length(513);
EXPECT_EQ(512, c.body_length());
}
-
我们可以看到,对于这类函数,用例编写很直接简单,步骤都是构造变量,再用合适的Google Test宏来验证变量值或者函数调用的返回值。
-
简单Mock
原函数
void chat_room::leave(chat_participant_ptr participant) {
participants_.erase(participant);
}
participants_类型是 std::set<chat_participant_ptr>。这个函数的目的很明显,将一个participant从set中移除。
真实地创建一个聊天参与者participant对象可能条件比较严苛或者成本比较高。为了有效率地验证这个函数,我们可以新建一些Mock的chat_participant_ptr而不用严格地去创建participant对象。
chat_participant 对象:
class chat_participant {
public:
virtual ~chat_participant() {}
virtual void deliver(const chat_message &msg) = 0;
};
Mock对象:
class mock_chat_participant : public chat_participant {
public:
MOCK_METHOD(void, deliver, (const chat_message &msg), (override));
};
测试用例:
TEST(ChatRoomTest, leave) {
auto p1 = std::make_shared<mock_chat_participant>(); //新建第一个Mock指针
auto p2 = std::make_shared<mock_chat_participant>(); //新建第二个Mock指针
auto p3 = std::make_shared<mock_chat_participant>(); //新建第三个Mock指针
auto p4 = std::make_shared<mock_chat_participant>(); //新建第四个Mock指针
chat_room cr; //新建待测试对象chat_room
cr.join(p1);
cr.join(p2);
cr.join(p3);
cr.join(p4);
EXPECT_EQ(cr.participants().size(), 4);
cr.leave(p4);
EXPECT_EQ(cr.participants().size(), 3);
cr.leave(p4);
EXPECT_EQ(cr.participants().size(), 3);
cr.leave(p2);
EXPECT_EQ(cr.participants().size(), 2);
}
Web请求
chat_room中有一个log(),依赖网络请求。原函数:
std::string chat_room::log() {
std::string* response;
this->requester->execute("request",response); // web访问,结果存在response指针中
return *response;
}
在单元测试中,我们只关心被测试部分的逻辑。为了测试这个函数,我们不应该创建真实的 requester,应该使用mock。
http_request对象:
class http_request {
public:
virtual ~http_request(){}
virtual bool execute(std::string request, std::string* response)=0;
};
Mock对象:
class mock_http_request : public http_request {
public:
MOCK_METHOD(bool, execute, (std::string request, std::string * response), (override));
};
测试用例:
TEST(ChatRoomTest, log) {
testing::NiceMock<mock_message_dao> mock_dao; //在下一部分会提到mock_message_dao
mock_http_request mock_requester; //Mock对象
std::string response = "response"; //期待调用函数的第二个参数将指向这个string对象
EXPECT_CALL(mock_requester, execute)
.WillRepeatedly( //每次调用都会(WillRepeatedly)执行
testing::DoAll( //每次执行包含多个行为
testing::SetArgPointee<1>(response),//将传入参数指针变量response指向response
testing::Return(true))); //返回值为true
chat_room cr
= chat_room(&mock_dao, &mock_requester); //将mock对象通过chat_room的constructor注入
EXPECT_EQ(cr.log(),"response"); //调用和Google Test断言
}
数据库访问
chat_room 对象会将聊天者发送的消息存储在redis中。当新用户加入时,chat_room对象从数据库获取所有历史消息发送给该新用户。
join函数:
void chat_room::join(chat_participant_ptr participant) {
participants_.insert(participant);
std::vector<std::string> recent_msg_strs =
this->dao->get_messages(); //从数据库中获取历史消息
for (std::string recent_msg_str: recent_msg_strs) {
//将每一个消息发送给该聊天参与者
auto msg = chat_message();
msg.set_body_string(recent_msg_str);
participant->deliver(msg);
}
}
message_dao对象:
class message_dao {
public:
virtual ~message_dao(){}
virtual bool add_message(std::string m)=0;
virtual std::vector<std::string> get_messages()=0;
};
Mock对象:
class mock_message_dao : public message_dao {
public:
MOCK_METHOD(bool, add_message, (std::string m), (override));
MOCK_METHOD(std::vector<std::string>, get_messages, (), (override));
};
测试用例:
EST(ChatRoomTest, join) {
mock_message_dao mock_dao; //创建mock对象(需要注入chat_room)
http_request_impl requester; //创建web访问对象(也需要注入chat_room)
auto mock_p1 = std::make_shared<mock_chat_participant>();
//创建participant的mock指针
EXPECT_CALL(mock_dao, get_messages)
.WillOnce(testing::Return(std::vector<std::string>{"test_msg_body_1", "test_msg_body_2", "test_msg_body_3"}));
//指定get_messages调用的返回值
EXPECT_CALL(*mock_p1, deliver).Times(3);
//指定deliver调用的次数
chat_room cr = chat_room(&mock_dao, &requester);
//创建chat_room对象,注入dao和requester
cr.join(mock_p1); //调用
}
四、FAQ
1、单元测试文件应该放在项目的什么位置?
一般来说,我们是会在根目录创建一个tests文件夹,里面放单元测试部分的源码,从而不会和被测试代码混在一起
如果需要和其他测试(如接口测试、压力测试)等区分开,可以:
1、把tests改成unittests、utests等
2、在tests创建不同的子文件夹存放不同类型的测试代码
2、Google Mock只能Mock虚函数,如果我想Mock非虚函数怎么办?
由于Google Mock(及其他大部分Mock框架)通过继承来动态重载机制的限制,一般来说Google Mock只能Mock 虚函数。如果要Mock非虚函数,官方文档提供这几种思路:
1、Mock类和原类没有继承关系,测试对象使用函数模板。在测试中,测试对象接收Mock类。
2、创建一个接口(抽象类),原类继承自这个接口(抽象类)。在测试中Mock这个接口(抽象类)。
这两种方法,都需要对代码进行一定的修改或重构。如果不想修改被测试代码。可以考虑使用hook技术替换被 Mock的部分从而Mock一般函数。
使用TMock对非虚函数Mock的例子:
mock函数:
# include "tmock.h"
class MockClass
{
public:
//注册mock类
TMOCK_CLASS(MockClass);
//声明mock类函数,TMOCK_METHOD{n}第一个参数与attach_func_lib第一个参数相同,其余参考与MOCK_METHOD{n}一致。
TMOCK_METHOD1("original", original, uint32_t(const char * str_file_md5) )
};
单测中应用tmock的方法和Google Mock基本一致。但在结束的时候需要使用TMOCK_CLEAR清除exception,detach hook的函数,防止干扰其他单元测试。
3、Google Test官方文档中说测试套件名称、测试夹具名称、测试名称中不应该出现下划线_,为什么?
TEST(TestSuiteName, TestName),生成名为TestSuiteName_TestName_Test的类。
下划线_是特殊的,因为C++保留以下内容供编译器和标准库使用。所以开头和结尾有下划线很容易让生成的类的标识符不合法。
另一方面,下划线可能让不同测试生成相同的类。比如TEST(Time, Files_Like_An_Arrow) {.....}都生成名为Time_Files_Like_An_Arrow_Test的类。
4、测试输出里有很多Uniteresting mock function call 警告怎么办?
创建的Mock对象的某些调用如果没有相应匹配的EXCEPT_CALL,Google Mock会生成这个警告。
为了去除这个警告,可以使用NiceMock。比如如果原本使用MockFoo nice_foo;新建mock对象的话,可以改成NiceMock nice_foo; NiceMock是MockFoo的子类。
五、实践小结
框架的使用,无非是一些语法糖的差异和使用的难易程度。不管使用什么语言,什么框架,最关键的是利用单元测试的思路,写出解耦的、可测试的、易于维护的代码,保证代码的质量。
单元测试是一种手段,能够一定程度的改善生产力。凡事有度,过犹不及。如果一味盲目的追求测试覆盖率,忽视了测试代码本身的质量。那么各种无效的单元测试反而带来了沉重的维护负担。因此单测的代码,本身也是代码,也是和项目本身的代码一样,需要重构、维护的。