Gtest Github
使用 gtest(gmock) 方便我们编写组织 c++ 单元测试。
到 github 拉取代码或者下载某个版本的 zip 包到本地目录,参考 gtest 中的 README.md 如何编译库和编译自己的代码,下面简单介绍下编译方法
$ g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \-pthread -c ${GTEST_DIR}/src/gtest-all.cc
$ ar -rv libgtest.a gtest-all.o
gtest 已经提供了 cmakelist,可以直接使用cmake 生成 makefile, 编译库和 sample
$ mkdir mybuild # Create a directory to hold the build output.
$ cd mybuild
$ cmake ${GTEST_DIR} # Generate native build scripts.
$ make
然后就可以在编译自己的测试程序时链接 gtest 了。
$ g++ -isystem ${GTEST_DIR}/include -pthread path/to/your_test.cc libgtest.a -o your_test
跟多详细内容参考 readme 和代码中提供的例子(samples ; make 目录下),比如如何解决重复定义宏等问题。
通过 编程参考 和 源码中 sample 目录下的示例
,我们可以很快上手 gtest。gtest 定义了宏供我们写断言语句,一个或者多个断言组成我们的测试用例 case,多个测试用例有时候需要共享一些通用对象,可以把这些用例放在同一个 fixture 中。
gtest 断言提供两个版本
* ASSERT_*
版本断言,在同一个 case 中(测试函数)中,ASSERT_* 失败就会终止当前用例,开始其他 case ;
* EXPECT_*
版本,当断言失败时,会报错,但是会继续执行剩余语句。
完整的 宏定义, 或见源码 include/gtest/gtest.h
使用哪种语句断言取决自己用例场景,如当前语句失败时后续语句没有继续执行意义,则可以直接使用 ASSERT 终止,否则使用 EXPECT 可以发现更多错误。
如果用例之间不需要什么公用资源,相互独立,可以使用如下方式定义每一个 case
TEST(套件名,用例名)
{//套件名和用例名自定义//断言语句//如一般的c++ 函数,不 return value
}
进入目录 sample 中, 以 sample1_unittest.cc 为例子
#include "sample1.h" // 测试对象头文件,接口
#include "gtest/gtest.h" // gtest 头文件TEST(IsPrimeTest, Negative) {EXPECT_FALSE(IsPrime(-1)) <<"这样子失败时打印自己的信息"; EXPECT_FALSE(IsPrime(-2)); // 如果此断言失败&#xff0c;还会继续执行下一个EXPECT_FALSE(IsPrime(INT_MIN));}TEST(IsPrimeTest, Negative) {EXPECT_FALSE(IsPrime(-1));ASSERT_FALSE(IsPrime(-2)); // 如果此断言失败&#xff0c;下一条不执行&#xff0c;这个case 结束EXPECT_FALSE(IsPrime(INT_MIN));}
编译修改的测试代码&#xff0c;其中 libgtest.a
是 gtest 的库。
g&#43;&#43; -isystem ../include/ ./sample1.cc ./sample1_unittest.cc -pthread ../libgtest.a ../libgtest_main.a
链接 libgtest_main.a
是为了使用 src/gtest_main.cc
中定义 main 函数&#xff0c;执行所用测试用例&#xff0c;否者&#xff0c;也可以自己定义 main。
#include
#include "gtest/gtest.h"
int main(int argc, char **argv) {printf("Running main() from gtest_main.cc\n");testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
编译后执行输出 bin 直接运行便运行所有用例&#xff0c;可以使用 -h
查看可选的执行参数&#xff0c;如--gtest_filter&#61;IsPrimeTest.Negative
指定执行 套件和 case ; --gtest_output&#61;xml[:DIRECTORY_PATH/|:FILE_PATH]
生成报告等。
多个用例需要使用相同的数据&#xff0c;每次都在用例中准备显得很重复麻烦&#xff0c;这时候&#xff0c;可以使用 Fixture 来构建用例&#xff0c;使多个用例共用相同的数据对象配置。
使用 Fiture 第一部是定义一个继承自::testing::Test
的类&#xff0c;在类中定义初始化函数&#xff0c;清理函数和声明需要使用的对象。
class QueueTest : public ::testing::Test { // 定义套件名&#xff0c;继承自 Testprotected: // 建议&#xff0c;子类可用成员//定义setup 函数&#xff0c;在每个用例执行前调用void SetUp() override {q1_.Enqueue(1);q2_.Enqueue(2);q2_.Enqueue(3);}// 定义清理函数&#xff0c;在每个用例执行后调用// void TearDown() override {}// 定义需要用到的变量Queue<int> q0_;Queue<int> q1_;Queue<int> q2_;
};//写用例&#xff0c;套件名(上面定义的类名)&#xff0c;用例名
TEST_F(QueueTest, IsEmptyInitially) {EXPECT_EQ(q0_.size(), 0); //直接使用成员变量
}
以上我们定义了一个套件 QueueTest &#xff0c; 当我们执行该套件用例时&#xff0c;
1. gtest 构建 QueueTest 实例 qt1&#xff1b;
2. 调用 qt1.SetUp() 初始化
3. 执行一个用例
4. 调用 qt1.TearDown() 清理
5. 析构 qt1 对象
6. 回到1&#xff0c;执行下一个用例
从步骤可知&#xff0c;不同用例之间&#xff0c;数据实际都是独占的&#xff0c;不会相互影响。
使用 fixture 编写用例后&#xff0c;同单独测试用例 TEST 一样&#xff0c;需要编写 main &#xff0c;然后编译连接&#xff0c;执行测试。
gmock 现在已经和入 gtest 的代码库, 1.8 和之后的版本直接在 gtest github 主页中获取&#xff0c;低版本仍然在原 github主页。
gmock 需要依赖 gtest 使用&#xff0c;在测试中&#xff0c;当我们测试的对象需要依赖其他模块、接口&#xff0c;但是往往受条件限制无法使用真实依赖的对象&#xff0c;通过 mock 对象来模拟我们需要依赖&#xff0c;以协助测试本模块&#xff0c;mock 对象具有和真实对象一样的接口&#xff0c;但是我们可以在运行时指定他的行为&#xff0c;如何被使用&#xff0c;使用多少次、参数&#xff0c;使用时返回什么等。
编译说明
gmock 编译需要依赖 gtest&#xff0c; 准备好 gtest 和 gmock &#xff08;同一个版本&#xff09;后&#xff0c;手动编译的方法如下&#xff1a;
设置好 gtest 和 gmock 的工程路径&#xff0c;或者在下面命令中直接替换源路径。
g&#43;&#43; -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \-isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \-pthread -c ${GTEST_DIR}/src/gtest-all.cc
g&#43;&#43; -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \-isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \-pthread -c ${GMOCK_DIR}/src/gmock-all.cc
ar -rv libgmock.a gtest-all.o gmock-all.o
由命令可知&#xff0c;libgmock.a 包含了 libgtest.a&#xff0c;所有实际编译测试程序时&#xff0c;只需要链接 libglmock.a 就好了。
使用 cmake编译库&#xff0c;进入 gmock 目录&#xff08;此处 gtest 已经准备并且与 gmock 同级目录&#xff09;
$ cd ./googlemock/; mkdir build
$ cd ./build; cmake ..
$ make
生成 libgmock.a 库在 build 目录下&#xff0c; 同时生成 libgtest.a gtest/
下&#xff0c; 与上面手动编译把 gtest 和 gmock 打在一个 libgmock.a 不同&#xff0c;使用这种编译程序需要同时指定 链接 libgmock.a 和 libgtest.a&#xff0c; 否则会报各种 undefine 的错误 。
编译测试程序 &#xff1a;
g&#43;&#43; -isystem ${GTEST_DIR}/include \-isystem ${GMOCK_DIR}/include \-pthread path/to/your_test.cc libgmock.a -o your_test
测试时&#xff0c;我链接 cmake 编译出来的库时报错&#xff0c;查看库中很多符号没有&#xff0c;原因就是 cmake 输出的 libmock.a 不包含 gtest&#xff0c;需要指定链接 libgtest.a
参考 gmock 编程指导和 codebook
gmock mock 对象&#xff0c;可以定义函数期望行为&#xff0c;如被调用时返回的值&#xff0c;期望被调用的次数&#xff0c;参数等&#xff0c;如果不满足就会报错。
定义 gmock 对象的基本步骤&#xff1a;
1. 创建 mock 对象继承自原对象&#xff0c;并用框架提供的宏 MOCK_METHODn(); (or MOCK_CONST_METHODn();
描述需要模拟的接口
2. 写用例&#xff0c;在用例中使用宏定义期望接口的行为&#xff0c;如果定义的行为执行用例时不满足&#xff0c;就会报错
借用主页提供的例子改写&#xff0c;简单学习下如何使用 mock
比如你测试的对象依赖的接口定义如下&#xff0c;
class Turtle {public:virtual ~Turtle() {}virtual void PenUp() &#61; 0;virtual void PenDown() &#61; 0;virtual void Forward(int distance) &#61; 0;virtual void Turn(int degrees) &#61; 0;virtual void GoTo(int x, int y) &#61; 0;virtual int GetX() const &#61; 0;virtual int GetY() const &#61; 0;};
此时通过继承这个对象&#xff0c;定义了 mock 对象&#xff0c;在对象中通过宏描述需要 mock 的接口&#xff0c;这样&#xff0c;就完成了对象的 mock 操作。
#include "gmock/gmock.h"
#include "gtest/gtest.hclass MockTurtle: public Turtle {
public:// MOCK_METHOD[参数个数](接口名&#xff0c;接口定义格式);MOCK_METHOD0(PenUp, void());MOCK_METHOD0(PenDown, void());MOCK_METHOD1(Forward, void(int distance));MOCK_METHOD1(Turn, void(int degrees));MOCK_METHOD2(GoTo, void(int x, int y));MOCK_CONST_METHOD0(GetX, int());MOCK_CONST_METHOD0(GetY, int());};
定义了 mock 对象后&#xff0c;就可以在测试用例使用 mock 对象替代原依赖对象&#xff0c;执行测试了。
using ::testing::AtLeast;TEST(PainterTest, PenDownCall) {MockTurtle turtle;EXPECT_CALL(turtle, PenDown())┊ .Times(AtLeast(2));// 期望这个函数在本次测试需要至少被调用2次// 否则报错turtle.PenDown();turtle.PenDown();}using ::testing::Return;TEST(PainterTest, GetX) {MockTurtle turtle;EXPECT_CALL(turtle, GetX())┊ .Times(4)┊ .WillOnce(Return(100))┊ .WillOnce(Return(150))┊ .WillRepeatedly(Return(200));// 期望这个函数在本次测试需要被调用4次// 否则报错// 第一次调用返回100&#xff0c; 第二次150&#xff0c;之后都是200EXPECT_EQ(turtle.GetX(), 100);EXPECT_EQ(turtle.GetX(), 150);EXPECT_EQ(turtle.GetX(), 200);EXPECT_EQ(turtle.GetX(), 200);}using ::testing::_;TEST(PainterTest, GoTo) {MockTurtle turtle;EXPECT_CALL(turtle, GoTo(_, 100));// 期望调用参数&#xff0c;第一个任意&#xff0c;第一个必须为 100turtle.GoTo(1, 100);EXPECT_CALL(turtle, GoTo(_, 101));turtle.GoTo(2, 101);}
gmock 使用宏设置期望是粘性的&#xff0c;意思是当我们调用达到期望后&#xff0c;这些设置的期望仍然保持活性。
举个例子&#xff0c;mock 一个接口 a(int)&#xff0c;我们设置第一个期望&#xff1a; a 调用传入参数任意&#xff0c;调用次数任意&#xff1b;然后设置第二个期望&#xff1a; a 调用传入参数必须为1&#xff0c; 调用次数为2&#xff1b;当我们调用 a(1) 两次后&#xff0c;达到了第二个期望上边界&#xff08;此时第二个期望并不会失效&#xff09;&#xff0c;这时候&#xff0c;第三次调用 a(1) 就会报错&#xff0c;因为匹配到第二个期望说调用超过2次。&#xff08;总是匹配最后一个期望&#xff09;
如果想设置多个期望&#xff0c;并按顺序执行&#xff0c;可以如下实现
//stickyTEST(PainterTest, GetY) {//设置调用按照期望设置顺序&#xff0c;定义一个 sq 对象&#xff0c;名随意using ::testing::InSequence;InSequence dummyObj;MockTurtle turtle;EXPECT_CALL(turtle, GetY())┊ .Times(2)┊ .WillOnce(Return(100))┊ .WillOnce(Return(150))┊ .RetiresOnSaturation(); // 指定匹配后不再生效&#xff0c;退休EXPECT_CALL(turtle, GetY())┊ .Times(1)┊ .WillOnce(Return(200))┊ .RetiresOnSaturation();EXPECT_EQ(turtle.GetY(), 100);EXPECT_EQ(turtle.GetY(), 150);EXPECT_EQ(turtle.GetY(), 200);}
最后&#xff0c;和 gtest 中一样&#xff0c;可以自己编写 main 函数完成调用&#xff0c;不过注意到&#xff0c;调用的 init 函数不同&#xff0c;之后便可以按前面提到的编译命令执行编译&#xff0c;运行测试了。
int main(int argc, char** argv) {//初始化 gtest 和 gmock::testing::InitGoogleMock(&argc, argv);return RUN_ALL_TESTS();}