GoogleTest
googletest: GoogleTest - Google Testing and Mocking Framework
googletest 是一个由 Google 的测试技术团队开发的测试框架,它考虑到了谷歌的特定需求和限制。无论你使用的是 Linux、Windows 还是 Mac,只要你编写 C++ 代码,googletest 都可以帮到你。它支持任何类型的测试,不只是单元测试。
- 独立的且可重复的(googletest 通过在不同的对象上运行每个测试用例来隔离测试。当测试失败时允许你单独运行它,以便快速调试);
- 可移植的且可复用的(googletest 适用于不同的操作系统,不同的编译器);
- 失败时,提供尽可能多的关于故障的信息(googletest 不会在第一个测试失败时停止。相反,它仅停止当前的测试并继续运行下一个);
- 得到良好的组织并反映测试代码的结构(googletest 将相关的测试分组为测试套件,它们可以共享数据和子例程);
- 测试框架将精力集中在测试内容上(googletest 自动追踪所有定义的测试,且无需用户以运行它们的顺序迭代它们);
- 快(通过 googletest,可以跨测试用例复用共享资源,且只支出一次 set-up/tear-down 的开销,不使测试相互依赖)。
任何可被送给
ostream
的都可以被送进断言的宏。
编译
gtest 提供了两种编译方式 bazel 和 cmake,以 cmake 为例:
mkdir build &&cd build
cmake ..# 只需要包含头文件 gtest.h或者gmock.h
断言
断言是 gtest 核心组成,所有的测试用例最终都是以断言实现的。
- ASSERT 系列的断言如果失败,则会产生一个严重错误并导致当前作用范围的测试用例中断;
- EXPECT 不会产生任何错误,测试用例依旧可以继续执行。
true / false 条件测试
致命非致命验证ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition: trueASSERT_FALSE(condition);EXPECT_FALSE(condition);condition: false
失败时,
ASSERT_*
产生一个致命错误,并从当前函数退出,
EXPECT_*
则产生一个非致命错误,并允许函数继续运行。
二元比较
致命断言非致命断言验证
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
- 比较 C 字符串,ASSERT_EQ 测试它们是否位于相同内存位置,而不是是否具有相同的值。因此,比较 C 字符串的值使用
ASSERT_STREQ()
;- 当执行指针比较时使用
*_EQ(ptr, nullptr)
和*_NE(ptr, nullptr)
而不是*_EQ(ptr, NULL)
和*_NE(ptr, NULL)
。
字符串比较
致命断言非致命断言验证ASSERT_STREQ(str1, str2);EXPECT_STREQ(str1, str2);具有相同内容ASSERT_STRNE(str1, str2);EXPECT_STRNE(str1, str2);具有不同内容ASSERT_STRCASEEQ(str1, str2);EXPECT_STRCASEEQ(str1, str2);相同内容忽略大小写ASSERT_STRCASENE(str1, str2);EXPECT_STRCASENE(str1, str2);不同内容忽略大小写
如果宽字符串(
wchar_t*
, Windows 上
UNICODE
模式的
TCHAR*
,或
std::wstring
)被送进断言,它将在打印时被转换为 UTF-8。
断言扩展
SUCCEED()
: 产生一个成功标识,只代表某一个步躁成功;
FAIL()
: 产生一个严重错误,相当于一个 ASSERT 宏失败;
ASSERT_THROW(expre,type)
: 断言表达式 expre 会抛出一个 type 类型异常;
ASSERTANY THROW(expre)
: 断言表达式 expre 会抛出一个任意类型异常;
ASSERT_NO_THROW(expre)
: 断言表达式 expre 不会抛出任何异常。
测试 TEST
- 使用 TEST() 宏定义并命名一个测试函数(没有返回值的宏函数);
- 解析命令行中的 GoogleTest 参数,它允许用户通过多样的命令行参数来控制测试程序的行为(即定制命令行参数的行为):
testing::InitGoogleTest(&argc, argv);
; - 搜索不同的 Test Case 和不同的源文件中所有已经存在的测试案例然后运行它们,成功时返回 1,否则返回 0:
return RUN_ALL_TESTS();
。
// 1. TestSuiteName 测试套件名// 2. TestName 测试用例名/// 名称不该包含下划线TEST(FactorialTest, HandlesZeroInput){EXPECT_EQ(Factorial(0),1);}TEST(FactorialTest, HandlesPositiveInput){EXPECT_NE(Factorial(1),1);}intmain(int argc, char** argv){testing::InitGoogleTest(&argc, argv);returnRUN_ALL_TESTS();}
Google C++ Style Guide 函数和类命名规则
全局事件
#include "gtest/gtest.h"classTestFA2:publictesting::Environment{public:voidSetUp() override {std::cout<<"SetUp"<<std::endl;}voidTearDown() override {std::cout<<"TearDown"<<std::endl;}};TEST(TestFA21, globalEnv){EXPECT_EQ(3,4)<<"int compare";}intmain(int argc, char** argv){testing::InitGoogleTest(&argc, argv);testing::Environment* env =newTestFA2();testing::AddGlobalTestEnvironment(env);int ret =0;
ret =RUN_ALL_TESTS();return ret;}
测试夹具 TEST_F
- 允许你为多个不同的测试复用相同的数据对象配置。
- googletest 不 为多个测试复用相同的测试夹具。
成员函数
虚函数
- virtual void SetUp():类似于构造函数,在 TEST_F 之前运行;
- virtual void TearDown():类似于析构函数,在 TEST_F 之后运行。
静态函数
- static void SetUpTestSuite():在第一个 TEST 之前运行;
- static void TearDownTestSuite():在最后一个 TEST 之后运行。
全局事件
继承 testing::Environment
- virtual void SetUp():在所有用例之前运行;
- virtual void TearDown():在所有用例之后运行。
创建
- 创建一个公有继承自 ::testing::Test 的类,进行 protected 类内部声明;
- 编写一个默认的构造函数或 SetUp() 函数为每个 test 准备对象;
- 编写一个析构函数或 TearDown() 函数释放 SetUp() 中分配的资源;
- 完善类资源信息,使用 TEST_F 宏,第一个参数填写新建夹具类名称。
由于 C++ 语法不允许在构造/析构函数中调用虚函数 SetUp/TearDown,建议优先使用构造/析构函数完成数据的初始化。
使用
- 使用 TEST_F(类名, 测试夹具名称) :
// TestFixtureName 测试夹具类名(_F:fixture)TEST_F(TestFixtureName, TestName){... test body ...}
通过 TEST_F() 定义的每个测试,googletest 将在运行时自动创建一个 全新的 测试夹具,立即通过 SetUp() 初始化它,运行测试,最后调用 TearDown() 清理资源,然后删除测试夹具。
具体类定义
template < typename E>// E is the element type.classQueue{public:Queue();voidEnqueue(constE& element);E*Dequeue();// Returns NULL if the queue is empty.
size_t size()const;...};
夹具类定义
#include "gtest/gtest.h"classTestFA:public::testing::Test{public:TestFA(){}~TestFA(){}protected:voidSetUp() override {std::cout<<"Reference count: "<< deq.use_count()<<std::endl;
auto p = deq.get();
auto pxin =*p;std::deque<int>a(5,-1);std::cout<<"1. size: "<< pxin.size()<<std::endl;
pxin.swap(a);std::cout<<"2. size:"<< pxin.size()<<std::endl;}voidTearDown() override {
deq.reset();
deq = nullptr;}public:std::shared_ptr<std::deque<int>> deq =std::make_shared<std::deque<int>>();};
测试定义
- 测试案例都来自同一个测试夹具;
- 不同的测试案例,拥有相互独立的个体,独占数据,不会相互影响。
TEST_F(TestFA, isEmptyOnB){EXPECT_GE(deq.use_count(),1);if(deq.use_count()!=0){
deq.reset();}}TEST_F(TestFA, isEmptyOnBIndependence){EXPECT_GE(deq.use_count(),1);}
测试调用
定义测试后,通过 RUN_ALL_TESTS() 运行它们,如果所有测试都成功,返回 0,否则返回 1。
调用 RUN_ALL_TESTS() 宏时:
- 保存所有 googletest 标记的状态;
- 为第一个测试创建一个测试夹具对象;
- 通过 SetUp() 初始化;
- 在测试夹具对象上运行测试;
- 通过 TearDown() 清理测试夹具;
- 删除夹具,恢复所有的 googletest 标记的状态;
- 为下一个测试重复上述步骤。
#include "gtest/gtest.h"intmain(int argc, char** argv){// 解析 googletest 标记的命令行参数,并移除所有已识别的标记testing::InitGoogleTest(&argc, argv);returnRUN_ALL_TESTS();}
调用 RUN_ALL_TESTS() 一次,多次调用将产生冲突。
接口测试 Gmock
Google C++单元测试框架GoogleTest—Google Mock简介–概念及基础语法 - 超超boy - 博客园
使用步骤
创建抽象类
- 创建抽象接口函数;
classB{public:
virtual constintsub(int s1,constint s2)const=0;};
创建 Mock 类继承
- 类返回作用域、参数作用域等,都需要进行描述;
#include "gmock/gmock.h"classMockB:publicB{public:MOCK_METHOD(constint, sub,(int,constint),(const, override));};
Mock 测试
- EXPECT_CALL(mock_object, method(matchers)) // 对象、匹配调用函数 .Times(cardinality) // 调用次数 .WillOnce(action) // 被调用一次时的行为 .WillRepeatedly(action); // 重复调用
#include "B.h"#include "gtest/gtest.h"
MockB b;TEST(mock_sub, actionBSub){EXPECT_CALL(b, sub).Times(::testing::AtLeast(3))// 3次需要3个WillOnce.WillOnce(::testing::Return(1)).WillOnce(::testing::Return(2)).WillOnce(::testing::Return(34));std::cout<< b.sub(2,1)<<std::endl;std::cout<< b.sub(3,1)<<std::endl;std::cout<< b.sub(55,44)<<std::endl;std::cout<< b.sub(34,0)<<std::endl;// 默认返回 0}// 参数匹配器TEST(mock_sub, expectCall){EXPECT_CALL(b,sub(123,123)).WillRepeatedly(Return(246));std::cout<< b.sub(123,1234)<<std::endl;// 默认 0std::cout<< b.sub(123,123)<<std::endl;// 符合,246,测试用例失效std::cout<< b.sub(123,123)<<std::endl;// 默认 0EXPECT_CALL(b,sub(Gt(123),_)).WillOnce(Return(1234));// 大于 123,和任意数值std::cout<< b.sub(124,123)<<std::endl;// 符合,-99std::cout<< b.sub(123,2)<<std::endl;// 0}// 顺序适配器TEST(mock_multi_sub, callInSequence){
Sequence s1;EXPECT_CALL(b, multi).InSequence(s1).WillOnce(Return(222));// 先EXPECT_CALL(b, sub).InSequence(s1).WillOnce(Return(333));// 后std::cout<< b.sub(124,123)<<std::endl;// 不符。为 0std::cout<< b.multi(123,2)<<std::endl;// 先,222std::cout<< b.sub(124,123)<<std::endl;// 后,333}
EXPECT_CALL
- Times :至少调用次数,小于则默认回调,如默认 int 返回 0;
- WillRepeatedly :重复调用,类似默认调用;
- ::testing::Gt(a) :函数参数匹配,设置匹配器,如当大于 a 时则匹配调用;
- InSequence :指定顺序适配器 Sequence 顺序调用成员函数;
示例
示例1:web 请求
std::stringchat_room::log(){std::string* response;
this->requester->execute("request",
response);// web访问,结果存在response指针中return*response;}classhttp_request{public:
virtual ~http_request(){}
virtual boolexecute(std::string request,std::string* response)=0;};classmock_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指向responsetesting::Return(true)));// 返回值为true
chat_room cr =chat_room(&mock_dao,&mock_requester);// 将mock对象通过chat_room的constructor注入EXPECT_EQ(cr.log(),"response");// 调用和Google Test断言}
示例2:数据库访问
voidchat_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);}}classmessage_dao{public:
virtual ~message_dao(){}
virtual booladd_message(std::string m)=0;
virtual std::vector<std::string>get_messages()=0;};classmock_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_1","test_2","test_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);// 调用}
注意
- 测试套件名称、测试夹具名称、测试名称中不应该出现下划线( TEST(TestSuiteName, TestName),生成名为TestSuiteName_TestName_Test 的类);
- Uniteresting mock function call 警告 : 没有相应匹配的EXCEPT_CALL,Google Mock 会生成这个警告,使用 NiceMock 进行 mock 类对象实例化;
- Mock 一般用于虚函数,通过 TMock 来 Mock 非虚函数;
- 单元测试文件可以放在根目录下面专用的 tests 文件夹下;
版权归原作者 belien 所有, 如有侵权,请联系我们删除。