Unit Test 和 gtest 介绍

单元测试Unit Test ,模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确,通过编写单元测试可以在编码阶段发现程序编码错误,甚至是程序设计错误。

单元测试不但可以增加开发者对于所完成代码的自信,同时,好的单元测试用例往往可以在 回归测试 的过程中,很好地保证之前所发生的修改没有破坏已有的程序逻辑。因此,单元测试不但不会成为开发者的负担,反而可以在保证开发质量的情况下,加速迭代开发的过程。

GoogleTest是一个跨平台的(LiunxMac OS XWindowsCygwinWindows CE and Symbian) C++ 单元测试框架,GoogleTestgoogle 公司发布, 且遵循 New BSD License(可用作商业用途)的开源项目, 为当前比较主流的 C++ 单元测试框架,目前所在公司也在使用。

gtest 安装、导入项目(Linux系统)

下载源码

我本地使用的系统参数:

bash-4.2$ uname -a
Linux yejy 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

gtest github 地址:

下载源码:

bash-4.2$ git clone https://github.com/google/googletest

导入项目

简单测试

下载源码后,接着就是将其导入到我们的项目中使用,如果你只是想简单测试一下,可以直接编译 gtest 源码,生成相应的静态库,将库和头文件拷贝到系统的头文件和库中,然后就可以直接写代码进行测试了,步骤如下:

bash-4.2$ cd googletest
bash-4.2$ cmake
bash-4.2$ make
bash-4.2$ cp libgtest*.a /usr/lib
bash-4.2$ cp –a include/gtest /usr/include

写一个简单的测试程序:

#include<gtest/gtest.h>

int add(int a, int b){
return a+b;
} TEST(testCase, test0){
EXPECT_EQ(add(4,3), 7); // 断言检测两参数是否相等
} int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv); // 初始化,所有测试都是这里启动的
return RUN_ALL_TESTS(); // 运行所有测试用例
}

编译代码,当然你可以用 make 或者 cmake 编译都可以,具体输出:

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from testCase
[ RUN ] testCase.test0
[ OK ] testCase.test0 (0 ms)
[----------] 1 test from testCase (0 ms total)

工业生产

上面这种测试方法比较特殊,等于是把 gtest 库和 gnu c 库一样使用了,正常工作项目中,肯定不会这样用的。

正确的做法是 以第三方库的形式直接将源码引入进项目。可能有人就会说了,为什么一定要将源代码引入其中,而不先编译出静态库,然后导入其中呢,这样编译自己项目的时候不就不用再重新编译了吗? 这里主要是考虑 跨平台,编译环境会有多种,需要多次编译,因此需要源码导入,同宿主项目一起编译。

我比较熟悉的编译工具是 cmake, 工作中使用的也是这个,该工具也是跨平台的,在编译大型跨平台项目时,很有优势,那这边就大致讲一下引入步骤,如果你对 cmake 很熟悉,那这边就很轻松了。

首先看一下引入后的代码结构,如下图:

重点是这个文件 unit_test/CMakeLists.txt

file(GLOB SRC_FILES ./*.cpp)
file(GLOB HEADER_FILES ./*.h) # 将给定目录添加到编译器用于搜索包含文件的目录中。相对路径被解释为相对于当前源目录。
# 相当于gcc命令的-I,告诉编译器到该目录中查找头文件
include_directories(${CMAKE_SOURCE_DIR}/src)
if(ENABLE_TEST)
include_directories(
${CMAKE_SOURCE_DIR}/3rdlib/googletest/googlemock/include
${CMAKE_SOURCE_DIR}/3rdlib/googletest/googletest/include
)
endif() # 生成可执行文件 posix_thread_test.exx
add_executable(posix_thread_test.exx
${SRC_FILES}
) # 引入 gtest 库,posixthread 为源代码库
target_link_libraries(posix_thread_test.exx
gtest
posixthread
) target_install(posix_thread_test.exx)

导入项目,主要就是看 unit_test/CMakeLists.txt 这个文件了,其他基本变化不大,如果你熟悉 cmake 很容易就能看懂。 至于图中的源码,是最近在封装 Posix-thread 时写的,源码大部分引用了陈硕老师的 muduo 网络库中的线程相关代码。

gtest 具体使用

介绍一下断言,断言主要用来做一些逻辑判断,主要有以下两类接口:

  • ASSERT_XXX(): 如果断言失败,则测试处理终止。
  • EXPECT_XXX(): 非致命性失败,允许继续处理。
Test Fatal NonFatal
condition 为真 ASSERT_TRUE(condition) EXPECT_TRUE(condition)
condition 为假 ASSERT_FALSE(condition) EXPECT_FALSE(condition)
Equal ASSERT_EQ(arg1,arg2) EXPECT_EQ(arg1,arg2)
Not Equal ASSERT_NE(arg1,arg2) EXPECT_NE(arg1,arg2)
Less Than ASSERT_LT(arg1,arg2) EXPECT_LT(arg1,arg2)
Less Than or Equal ASSERT_LE(arg1,arg2) EXPECT_LE(arg1,arg2)
Greater Than ASSERT_GT(arg1,arg2) EXPECT_GT(arg1,arg2)
Greater Than or Equal ASSERT_GE(arg1,arg2) EXPECT_GE(arg1,arg2)
C String Equal ASSERT_STREQ(str1,str2) EXPECT_STREQ(str1,str2)
C String Not Equal ASSERT_STRNE(str1,str2) EXPECT_STRNE(str1,str2)
C String Case Equal ASSERT_STRCASEEQ(str1,str2) EXPECT_STRCASEEQ(str1,str2)
C String Case Not Equal ASSERT_STRCASENE(str1,str2) EXPECT_STRCASENE(str1,str2)
Verify that exception is thrown ASSERT_THROW(statement,exception_type) EXPECT_THROW(statement,exception_type)
Verify that exception is thrown ASSERT_ANY_THROW(statement) EXPECT_ANY_THROW(statement)
Verify that exception is NOT thrown ASSERT_NO_THROW(statement) EXPECT_NO_THROW(statement)

测试代码如下:

#include <gtest/gtest.h>
#include <posix_thread.h> void threadFunc()
{
std::cout << "tid= "<< PosixThread::CurrentThread::tid() << std::endl;
} TEST(PosixThreadTest, CreateThread)
{
std::cout << "pid= " << ::getpid() << " tid= " <<PosixThread::CurrentThread::tid() << std::endl; PosixThread::Thread t1(threadFunc);
t1.start();
ASSERT_TRUE(t1.started());
EXPECT_FALSE(t1.started()); // 故意失败
ASSERT_FALSE(t1.started()); // 故意失败
std::cout << "t1.tid: " << t1.tid() << std::endl;
std::cout << "thread name: " << t1.name().c_str() << std::endl; t1.join(); std::cout << "CreateThread end !\n"
<< std::endl;
} TEST(AtomicTest, AtomicInt64)
{
std::cout << "pid= " << ::getpid() << " tid= " <<PosixThread::CurrentThread::tid() << std::endl; PosixThread::AtomicInt64 a0;
ASSERT_EQ(a0.get(), 0);
ASSERT_EQ(a0.getAndAdd(1), 0);
ASSERT_EQ(a0.get(), 1);
ASSERT_EQ(a0.addAndGet(2), 3);
ASSERT_EQ(a0.get(), 3);
ASSERT_EQ(a0.incrementAndGet(), 4);
ASSERT_EQ(a0.get(), 4);
a0.increment();
ASSERT_EQ(a0.get(), 5);
ASSERT_EQ(a0.addAndGet(-3), 2);
ASSERT_EQ(a0.getAndSet(100), 2);
ASSERT_EQ(a0.get(), 100);
}

执行结果:

bash-4.2$ ./output/bin/posix_thread_test.exx
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from PosixThreadTest
[ RUN ] PosixThreadTest.CreateThread
pid= 5297 tid= 5297
tid= 5298
/home/willy/myshare/thread-pool/unit_test/thread_test.cpp:16: Failure
Value of: t1.started()
Actual: true
Expected: false
/home/willy/myshare/thread-pool/unit_test/thread_test.cpp:17: Failure
Value of: t1.started()
Actual: true
Expected: false
[ FAILED ] PosixThreadTest.CreateThread (0 ms)
[----------] 1 test from PosixThreadTest (0 ms total) [----------] 1 test from AtomicTest
[ RUN ] AtomicTest.AtomicInt64
pid= 5297 tid= 5297
[ OK ] AtomicTest.AtomicInt64 (0 ms)
[----------] 1 test from AtomicTest (0 ms total) [----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (0 ms total)
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] PosixThreadTest.CreateThread 1 FAILED TEST

从执行结果,我们可以很清楚的知道测试用例具体执行到哪一步,如果失败了,我们可以看到具体是哪一行代码出问题了,程序预期结果是什么,但是实际结果又是什么,输出十分详细。

我们还可以将测试结果导出到 xml 文件,通过参数:--gtest_output 实现。

bash-4.2$ ./output/bin/posix_thread_test.exx --gtest_output="xml:./test.xml"
bash-4.2$ cat test.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="2" failures="1" disabled="0" errors="0" timestamp="2019-01-04T21:36:40" time="0" name="AllTests">
<testsuite name="PosixThreadTest" tests="1" failures="1" disabled="0" errors="0" time="0">
<testcase name="CreateThread" status="run" time="0" classname="PosixThreadTest">
<failure message="/home/willy/myshare/thread-pool/unit_test/thread_test.cpp:16 Value of: t1.started() Actual: true Expected: false" type=""><![CDATA[/home/willy/myshare/thread-pool/unit_test/thread_test.cpp:16
Value of: t1.started()
Actual: true
Expected: false]]></failure>
<failure message="/home/willy/myshare/thread-pool/unit_test/thread_test.cpp:17 Value of: t1.started() Actual: true Expected: false" type=""><![CDATA[/home/willy/myshare/thread-pool/unit_test/thread_test.cpp:17
Value of: t1.started()
Actual: true
Expected: false]]></failure>
</testcase>
</testsuite>
<testsuite name="AtomicTest" tests="1" failures="0" disabled="0" errors="0" time="0">
<testcase name="AtomicInt64" status="run" time="0" classname="AtomicTest" />
</testsuite>
</testsuites>

此外,在运行可执行目标程序时,可以使用 --gtest_filter 来指定要执行的测试用例,支持字符串正则匹配,主要如下几种常用情况:

./output/bin/posix_thread_test.exx 没有指定filter,运行所有测试;
./output/bin/posix_thread_test.exx --gtest_filter=* 指定filter为*,运行所有测试;
./output/bin/posix_thread_test.exx --gtest_filter=PosixThreadTest.* 运行测试用例FooTest的所有测试;
./output/bin/posix_thread_test.exx --gtest_filter=*Null*:*Thread* 运行所有全名;
./output/bin/posix_thread_test.exx --gtest_filter=PosixThreadTest.*-PosixThreadTest.CreateThread
运行测试用例FooTest的所有测试,但不包括PosixThreadTest.CreateThread。

gtest 还有很多方便你测试的功能,包括 事件机制, 参数化, 死亡测试, 运行参数等,我们点到为止,如果想继续深入,可以参考这位博主的 gtest 系列, 很详细:

玩转Google开源C++单元测试框架Google Test系列

googlemock 使用

googlemock,是用于编写和使用C++ 模拟类的框架,在我们工作中,主要用来模拟应用程序的一部分,在单元测试用例编写过程中,常常需要编写模拟对象来隔离被测试单元的“下游”或“上游”程序逻辑或环境,从而达到对需要测试的部分进行隔离测试的目的,它可以帮助我们获得更好的系统设计并编写更好的测试。googlemock 同样遵循 New BSD License(可用作商业用途)的开源项目。

在开发过程中,经常出现各联调模块间,进度不一的情况;测试环境非常不稳定,易导致测试失败,导致达不到单元测试的目的,模仿对象提供了解决这些问题的方法:模仿对象符合实际对象的接口,但只包含用来“欺骗”测试对象并跟踪其行为的必要代码。因此,其实现往往比实际实现类简单很多。

官方教程:

官方的 Tutorial 讲的很详细,我在github上也找了一个使用例子,很简洁,但是能很好的说明问题,大致代码如下:

mail_service.h文件:

#ifndef MAIL_SERVICE_HPP
#define MAIL_SERVICE_HPP /** \brief Mail service. This represents one of the collaborators of the SUT.
* \author David Stutz
*/
// 邮件服务
class MailService
{
public:
/** \brief Send a mial.
* \param[in] message message to send
*/
virtual void send(std::string message) = 0; }; #endif /* MAIL_SERVICE_HPP */

order.h文件:

#ifndef ORDER_HPP
#define ORDER_HPP #include <string>
#include <memory>
#include "warehouse.h"
#include "mail_service.h" /** \brief An order of a product with quantity. */
// 订单
class Order
{
public:
/** \brief Constructor.
* \param[in] quantity quantity requested
* \param[in] product product name requested
*/
Order(int quantity, std::string product)
{
this->quantity = quantity;
this->product = product;
} /** \brief Set the mail service to use.
* \param[in] mailService the mail service to attach
*/
// 设置邮件服务
void setMailService(std::shared_ptr<MailService> mailService)
{
this->mailService = mailService;
} /** \brief Fill the order given the warehouse.
* \param[in] warehouse the warehouse to use
* \return whether the operation was successful
*/
// 判断产品是否有库存,发送邮件通知
bool fill(Warehouse &warehouse)
{
if (warehouse.hasInventory(quantity, product))
{
// ...
warehouse.remove(quantity, product);
this->mailService->send("Order filled."); return true;
}
else
{
// ...
this->mailService->send("Order not filled."); return false;
}
} private: /** \brief Product name. */
std::string product; /** \brief Quantity requested. */
int quantity; /** \brief Mail service to use. */
std::shared_ptr<MailService> mailService;
}; #endif /* ORDER_HPP */

warehouse.h文件:

#ifndef WAREHOUSE_HPP
#define WAREHOUSE_HPP #include <string> /** \brief Warehouse interface. This interface is one of the collaborators of our SUT.
* \author David Stutz
*/
class Warehouse
{
public:
/** \brief Check whether the product in the given quantity is on stock.
* \param[in] quantity quantity requested
* \param[in] product product name
* \return whether the warehouse has the product on stock for the given quantity
*/
// 是否有库存
virtual bool hasInventory(int quantity, std::string product) const = 0; /** \brief Remove the given quantity of the product from the warehouse.
* \param[in] quantity quantity to remove
* \param[in] product product name to remove
*/
// 从库存中删除
virtual void remove(int quantity, std::string product) = 0; }; #endif /* WAREHOUSE_HPP */

主要场景就是处理产品订单,其中库存Warehouse类和邮件服务MailService类,我们只声明一下虚基类,不实现,然后通过模拟对象的方式mock一下Warehouse和MailService,来达到订单类接口测试的正常开展,具体测试代码:

#include <gmock/gmock.h>
#include "lib/mail_service.h"
#include "lib/order.h"
#include "lib/warehouse.h" using ::testing::Return;
using ::testing::_; // Matcher for parameters class MockWarehouse : public Warehouse
{
public: // see https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md
MOCK_CONST_METHOD2(hasInventory, bool(int, std::string));
MOCK_METHOD2(remove, void(int, std::string));
}; class MockMailService : public MailService
{
public:
MockMailService()
{ } MOCK_METHOD1(send, void(std::string));
}; TEST(OrderTest, Fill)
{
MockWarehouse warehouse;
std::shared_ptr<MockMailService> mailService = std::make_shared<MockMailService>(); Order order(50, "Talisker");
order.setMailService(mailService); EXPECT_CALL(warehouse, hasInventory(50, "Talisker"))
.Times(1)
.WillOnce(Return(true)); EXPECT_CALL(warehouse, remove(50, "Talisker"))
.Times(1); EXPECT_CALL(*mailService, send(_)) // Not making assumptions on the message send ...
.Times(1); ASSERT_TRUE(order.fill(warehouse));
} int main(int argc, char **argv)
{
testing::InitGoogleMock(&argc, argv); // Runs all tests using Google Test.
return RUN_ALL_TESTS();
}

测试结果:

bash-4.2$ ./output/bin/order.exx
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from OrderTest
[ RUN ] OrderTest.Fill
[ OK ] OrderTest.Fill (0 ms)
[----------] 1 test from OrderTest (0 ms total) [----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[ PASSED ] 1 test.

其中main函数和gtest差不多,只是初始化的是googlemock,我们着重了解的是几个宏的含义:

MOCK_METHOD

MOCK_METHOD#1(#2, #3(#4) )

MOCK_CONST_METHOD2(hasInventory, bool(int, std::string));
MOCK_METHOD2(remove, void(int, std::string));

其中#1表示你要mock的方法共有几个参数,#2是你要mock的方法名称,#3表示这个方法的返回值类型,#4是这个方法具体的参数。

EXPECT_CALL

using ::testing::Return;
EXPECT_CALL(warehouse, hasInventory(50, "Talisker"))
.Times(1)
.WillOnce(Return(true));

设定期望对象被访问的方式及其响应,其中warehouse为对象,希望hasInventory在传递参数为(50, “Talisker”)时,被调用且仅被调用一次,第一次返回true。

ON_CALL

ON_CALL(#1, #2(#3)).WillByDefault(Return(#4));

ON_CALL(foo, GetSize())
.WillByDefault(Return(1));
// ... other default actions ...

其中#1表示mock对象,#2表示个方法名称,#3表示方法的参数,#4表示参数为#1, #2,#3情况下返回结果。

ON_CALLEXPECT_CALL的区别? ON_CALL定义了调用mock方法时会发生什么,但并不意味着对被调用方法的任何期望。 EXPECT_CALL不仅定义了行为,还设置了对给定次数(以及在指定顺序时按给定顺序)使用给定参数调用方法的期望。

GoogleMock 为开发者设定 Mock 类行为,跟踪程序运行过程及结果,提供了丰富的支持。但与此同时,应用程序也应该尽量降低应用代码间的耦合度,使得单元测试可以很容易对被测试单元进行隔离。(尽量做到高内聚,低耦合)

总结

Googletest 与 GoogleMock,很好的简化了我们的C++单元测试工作,本篇文章对此做了一个总结,让自己对gtest有了一个系统的认识。测试并不只是测试工程师的责任,对于开发工程师,为了保证发布给测试环节的代码具有足够好的质量( Quality ),为所编写的功能代码编写适量的单元测试是十分必要的。

如果还想更加深入的了解,可查阅官方文档

参考链接

https://www.ibm.com/developerworks/cn/linux/l-cn-cppunittest/?mhq=gtest&mhsrc=ibmsearch_a

https://blog.csdn.net/russell_tao/article/details/7344739

http://www.cnblogs.com/coderzh/archive/2009/03/31/1426758.html

https://github.com/davidstutz/googlemock-example

【3rd_Party】Cpp 单元测试框架-gtest的更多相关文章

  1. Google单元测试框架gtest之官方sample笔记2--类型参数测试

    gtest 提供了类型参数化测试方案,可以测试不同类型的数据接口,比如模板测试.可以定义参数类型列表,按照列表定义的类型,每个测试case都执行一遍. 本例中,定义了2种计算素数的类,一个是实时计算, ...

  2. Google单元测试框架gtest之官方sample笔记3--值参数化测试

    1.7 sample7--接口测试 值参数不限定类型,也可以是类的引用,这就可以实现对类接口的测试,一个基类可以有多个继承类,那么可以测试不同的子类功能,但是只需要写一个测试用例,然后使用参数列表实现 ...

  3. Google单元测试框架gtest之官方sample笔记4--事件监控之内存泄漏测试

    sample 10 使用event listener监控Water类的创建和销毁.在Water类中,有一个静态变量allocated,创建一次值加一,销毁一次值减一.为了实现这个功能,重载了new和d ...

  4. 简单易懂的单元测试框架-gtest(一)

    简介     gtest是google开源的一个单元测试框架,以其简单易学的特点被广泛使用.该框架以第三方库的方式插入被测代码中.同其他单元测试框架相似,gtest也通过制作测试样例来进行代码测试.同 ...

  5. C++单元测试框架gtest使用

    作用 作为代码编码人员,写完代码,不仅要保证编译通过和运行,还要保证逻辑尽量正确.单元测试是对软件可测试最小单元的检查和校验.单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成 ...

  6. Google C++单元测试框架---Gtest框架简介(译文)

    一.设置一个新的测试项目 在用google test写测试项目之前,需要先编译gtest到library库并将测试与其链接.我们为一些流行的构建系统提供了构建文件: msvc/ for Visual ...

  7. 简单易懂的单元测试框架-gtest(二)

    简介     事件机制用于在案例运行前后添加一些操作(相当于挂钩函数).目前,gtest提供了三种等级的事件,分别: 全局级,所有案例执行的前后 TestSuite级,某一个案例集的前后 TestCa ...

  8. Google单元测试框架gtest之官方sample笔记1--简单用例

    1.0 通用部分 和常见的测试工具一样,gtest提供了单体测试常见的工具和组件.比如判断各种类型的值相等,大于,小于等,管理多个测试的测试组如testsuit下辖testcase,为了方便处理初始化 ...

  9. Google C++单元测试框架GoogleTest(总)

    之前一个月都在学习googletest框架,对googletest的文档都翻译了一遍,也都发在了之前的博客里,另外其实还有一部分的文档我没有发,就是GMock的CookBook部分:https://g ...

  10. Google C++单元测试框架GoogleTest---GTest的Sample1和编写单元测试的步骤

    如果你还没有搭建gtest框架,可以参考我之前的博客:http://www.cnblogs.com/jycboy/p/6001153.html.. 1.The first sample: sample ...

随机推荐

  1. [ABC264F] Monochromatic Path

    Problem Statement We have a grid with $H$ rows and $W$ columns. Each square is painted either white ...

  2. [ABC281F] Xor Minimization

    div class="part"> Problem Statement You are given a sequence of non-negative integers $ ...

  3. docker开启或关闭<开机自启容器>

    启动容器时设置 docker run --restart=always 启动完成也可以修改 docker update --restart=always <容器ID> 想取消容器自启 do ...

  4. 组合式api的使用方式

    方式一:通过setup选项 <script> export default { setup(){ // 提供数据 // 提供函数 // 提供计算属性等等..... } } </scr ...

  5. Scrapy自带的断点续爬JOB-DIR参数

    参考官方文档:https://docs.scrapy.org/en/latest/topics/jobs.html?highlight=JOBDIR#jobs-pausing-and-resuming ...

  6. 从零玩转Nginx

    01[熟悉]实际开发中的问题? 现在我们一个项目跑在一个tomcat里面 当一个tomcat无法支持高的并发量时.可以使用多个tomcat 那么这多个tomcat如何云分配请求 |-nginx 02[ ...

  7. Redis 分片集群

    1.Redis分片集群 1.1.搭建分片集群 主从和哨兵可以解决高可用.高并发读的问题.但是依然有两个问题没有解决: 海量数据存储问题 高并发写的问题 使用分片集群可以解决上述问题,如图: 分片集群特 ...

  8. 2.elasticsearch中的mapping

    mapping 顾名思义,代表了映射关系.是文档中字段和数据类型的映射关系 为什么要了解mapping 虽然elasticsearch中已尽有的动态mapping(Dynamic Mapping),而 ...

  9. grafana添加组件

    ###安装grafana插件需联网安装[root@zabbix grafana]# grafana-cli plugins list-remote #查询可用的插件id: abhisant-druid ...

  10. 面试官:单例Bean一定不安全吗?实际工作中如何处理此问题?

    默认情况下,Spring Boot 中的 Bean 是非线程安全的.这是因为,默认情况下 Bean 的作用域是单例模式,那么此时,所有的请求都会共享同一个 Bean 实例,这意味着这个 Bean 实例 ...