Google Test 是一套编写C++单元测试的框架,可以运行在很多平台上。

如何安装

$ git clone https://github.com/google/googletest.git
$ cd googletest
$ mkdir build
$ cd build
$ cmake ..
$ make
$ sudo make install

安装之后,如何在代码中使用呢?用一个Cmake脚本说明一下:

cmake_minimum_required (VERSION 2.8)project(gtest_demo)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -Wall")find_package(GTest REQUIRED)find_library(GMOCKNAMES gmockPATH /usr/local/lib /usr/lib)include_directories(${GTEST_INCLUDE_DIRS})
add_executable(MyTests test_main.cpp test.cpp)
target_link_libraries(MyTests ${GMOCK} ${GTEST_BOTH_LIBRARIES})
add_test(Test MyTests)
enable_testing()

OK,上面的cmake还引入了gmock,gmock中包含了gtest,它做的事情更加酷炫(例如mock、Hamcrest断言等等)。

接着编写test_main.cpp文件

#include <gmock/gmock.h>
#include <iostream>int main(int argc, char* argv[])
{::testing::InitGoogleMock(&argc, argv);RUN_ALL_TESTS();
}

test.cpp中编写测试的代码

#include <gmock/gmock.h>
using namespace testing;TEST(AddTest, OnePlusOneEqTwo)
{ASSERT_THAT(1+1, Eq(2));
}

断言

本质上,GTest通过断言帮助你构建自动化测试的代码。断言的结果有三种:成功、非终止失败和终止失败。其中,终止失败会终止当前的测试函数

断言有两种,

  • ASSERT_* 失败是会终止当前函数,ASSERT_* 后面的代码将不会运行
  • EXPECT_* 失败时不会终止,EXPECT_* 后面的代码将会继续运行

通常,一个测试单元如果只有一个断言,使用ASSERT_* ,如果有多个断言则使用 EXPECT_*

经典式断言

GTest支持两种断言形式,一种是经典式的断言。它们沿袭了SUnit的风格,使用这种形式并无不妥,但是你或许会考虑形式更加多样的断言形式,即 Hamcrest 断言。由于你会遇到大量的经典式代码,因此学习这两种断言是十分必要。

下表列出了经典式断言的两个主要断言。其他框架也会使用类似的名称

形式 描述 实例
ASSERT_TRUE(表达式) 表达式返回假(或者0),测试失败 ASSERT_TRUE(4<7)
ASSERT_EQ(期待值,实际值) 期待值和实际值不等时,测试失败 ASSERT_EQ(4, 20/5)

GTest还提供了一些额外的断言形式来提高表达能力,ASSERT_* 一共有八种断言形式,分别是:ASSERT_TRUE()ASSERT_FALSE()ASSERT_EQ()ASSERT_NE()ASSERT_LT()ASSERT_LE()ASSERT_GT()ASSERT_GE()

EXPECT_* 与之类似,也有八种。

Hamcrest 断言

Hamcrest 断言是为了提高测试的表达能力,创建复杂断言的灵活性,以及测试错误所提供的信息

Hamcrest 断言使用匹配器比较实际结果。匹配器可以组成复杂但易懂的比较表达式。你也可以自己定义匹配器。

几个简单的示例胜过千言万语

TEST(StringTest, StringEq)
{string actual = string("al") + "pha";ASSERT_THAT(actual, Eq("alpha"));
}

断言可以从左到右读作:断定实际值等于"alpha"。对比与 ASSERT_EQ(actual, "alpha"),Hamcrest 断言用区区几个额外的字符就提高了阅读性。

起初,Hamcrest 断言貌似太过于炫技。但是匹配器的价值在于它能极大地提升测试的表达能力,许多匹配器能够减少所需的代码量,同时也能提升测试的抽象层次。

ASSERT_THAT(actual, StartsWith("alx"));

Google Mock的文档中列出了一些内置的匹配器

使用的时候需要添加using声明。
using namespace ::testing

否则原本用来提升表达能力的断言读起来有点啰嗦卡顿。例如:
ASSERT_THAT(actual, ::testing::StartsWith("alx"));

Hamcrest断言在提升失败信息的可读性方面意义更大。

Value of: actual
Expected: starts with "alx"Actual: "alpha"

匹配器的组合能力使你用一行断言就能表达本来需要多行断言才能做到的事情。

TEST(StringTest, ALLOf)
{string actual = string("al") + "pha";ASSERT_THAT(actual, AllOf(StartsWith("al"), EndsWith("ha"), Ne("aloha")));
}

上面的例子的AllOf表明所有匹配器都成功的时候,整个断言才通过。因而actual必须以"al"开头,以"ha"结尾,且不等于"aloha"

对于布尔值的断言而言,大部分开发者都会避免使用Hamcrest断言而直接使用经典形式的断言

ASSERT_TRUE(someBoolenExpression);
ASSERT_FALSE(someBoolenExpression);

fixture

大多数单元测试的文件都支持将逻辑相关的测试进行分组。在Google Mock中,你可以使用测试用例名称将测试分组。例如,下面的测试用例名为 AVector,而EmptyWhenInit是此测试用例的中的一个测试
TEST(AVector, EmptyWhenInit)

相关的测试可能需要相同的测试环境。你会发现许多测试都需要公共的初始化或者辅助函数

TEST(AVector, EmptyWhenInit)
{vector<int> vec;ASSERT_TRUE(vec.empty());
}TEST(AVector, NotEmptyAfterPush)
{vector<int> vec;ASSERT_TRUE(vec.empty());
}

上面两个测试用例都用到了一个vector

为此,我们可以定义一个fixture——跨测试可重用的类。在Google Mock中,你可以定义一个派生自::testing::Testfixture,通常在测试文件开始定义一个fixture

using namespace ::testing;
class AVector : public Test
{
public:vector<int> vec;
};

fixture中我们定义重用的数据,然后将宏TEST改成TEST_F,下面是经过整理后的测试:

TEST_F(AVector, EmptyWhenInit)
{ASSERT_TRUE(vec.empty());
}TEST_F(AVector, NotEmptyAfterPush)
{vec.push_back(0);ASSERT_FALSE(vec.empty());
}

注意:测试用例的名字必须和fixture的名称一样,如果没有带_F的宏,编译就会出错

SetUp 和 TearDown

如果测试用例中所有的测试都有一条或者更多调相同的初始化语句,那么可以将它们写在fixture中初始化函数中,在Google Mock中,必须将这个函数命名为SetUp(覆盖了::testing::Test中的虚函数)

对于fixture中的每一个测试,Google Mock都会创建一个新的,独立的 fixture 实例。这种隔离有助于减少测试之间相互干扰的情况,这也暗示着每个测试都从头创建自己的上下文,并且这些上下文是相互独立的。在创建 fixture 后,Google Mock会执行SetUp函数,然后执行测试

下面的例子都用了vec.push_back(0)初始化上下文

TEST_F(AVectorContainOneElement, NotEmpty)
{vec.push_back(0);ASSERT_FALSE(vec.empty());
}TEST_F(AVectorContainOneElement, EmptyAfterPop)
{vec.push_back(0);vec.pop_back();ASSERT_TRUE(vec.empty());
}

我们可以将初始化的工作放进SetUp

class AVectorContainOneElement : public Test
{
public:void SetUp() override{vec.push_back(0);}vector<int> vec;
};TEST_F(AVectorContainOneElement, NotEmpty)
{ASSERT_FALSE(vec.empty());
}TEST_F(AVectorContainOneElement, EmptyAfterPop)
{vec.pop_back();ASSERT_TRUE(vec.empty());
}

我们移除了重复的代码,这样有助于我们理解测试用例。

TearDown函数本质上是SetUp的逆过程。每个测试执行后,它都会执行一次,即便当前测试抛出异常也不例外。你可以将TearDown用于清理工作:释放内存等。

常用的断言写法

在使用的Google Mock的过程中,我们断言的对象常常是复杂的,例如是浮点类型的或者是一个容器,如何对这样的数据做断言呢?这里,列举了常用的几种断言

浮点数比较

浮点数一直都是个麻烦的事情,好在Google Mock提供了内置的Floating-point 匹配器,能让我们方便的对浮点类型进行断言

ASSERT_THAT(1.0f, FloatEq(1));
ASSERT_THAT(1.0, DoubleEq(1));

还可以设置误差范围:

ASSERT_THAT(1.0f, FloatNear(1.0000001, 1e-6));
ASSERT_THAT(1.0, DoubleNear(1.0000001, 1e-6));

有时候要判断是否为nan

ASSERT_THAT(1.0f/0, NanSensitiveFloatEq(1.0/0));
ASSERT_THAT(1.0/0, NanSensitiveDoubleEq(1.0/0));

容器比较

STL中大部分容器都支持 ==,所以你可以用Eq(expected_container)来对比容器

vector<int> a = {1,2,3};
vector<int> b = {1,2,3};
ASSERT_THAT(a, Eq(b));

或者,可以直接判断容器中的元素

vector<int> a = {1,2,3};
ASSERT_THAT(a, ElementsAre(1,2,3));

有时候,我们不关心容器中元素的顺序

vector<int> a = {1,2,3};
ASSERT_THAT(a, UnorderedElementsAre(2,1,3));

指针比较

两只指针的地址比较,直接用Eq就可以了,如果要比较指针指向的值,可以用Pointee

int* a = new int(1);
ASSERT_THAT(a, Pointee(1));delete a;

好吧,我知道你想问,为什么不用ASSERT_THAT(*a, Eq(1))。这么用当然没有问题,但是使用Pointee在可读性上要好得多,符合人们的阅读习惯

对象的比较

想要比较两个对象是否相等,需要以全局函数的形式重载==

class Foo
{
public:Foo(int x):x_(x){}int getX() const{return x_;}
private:int x_;
};bool operator==(const Foo& lhs, const Foo& rhs)
{return lhs.getX() == rhs.getX();
}TEST(ClassFooTest, EqualsIfHavaSameX)
{Foo a(0);Foo b(0);ASSERT_THAT(a, Eq(b));
}

同理,重载>,<,>=,<=之类的就比较大小关系了

自己写匹配器

当内置的匹配器都不能满足你的需求时,那么你需要动手自己写匹配器,直接上几个例子辅助理解匹配器怎么写

MATCHER(IsEven, ""){return (arg % 2) == 0;}
TEST(ANum, IsEven)
{ASSERT_THAT(4, IsEven());
}
MATCHER_P(IsDivisibleBy, n, ""){ return (arg % n) == 0; }
TEST(ANum, IsDivisibleByThree)
{ASSERT_THAT(6, IsDivisibleBy(3));
}
MATCHER_P2(InCloseRange, low, high, ""){return low <= arg && arg <= high;
}
TEST(InCloseRange, 1)
{ASSERT_THAT(3, InCloseRange(4, 5));
}

可以看到,MATCHER表示匹配器不需要参数,MATCHER_P需要一个参数,MATCHER_P2需要两个参数,以此类推,最多能到MATCHER_P10

MATCHER中最后的那个字符串在测试失败出现,如果为空,Google Mock会默认帮你生成一个

arg是要比较的对象,有时候它是个tuple,例如在用到Pointwise时。考虑一个问题,如何对比浮点型的数组,并且允许有一定的误差,你可以这样写:

MATCHER_P(NearWithPrecision, ferr, "")
{return abs(get<0>(arg) - get<1>(arg)) < ferr;
}TEST(FloatArrayTest, Eq)
{vector<float> float_array = {1.000001f, 2.000002f, 3.000003f};vector<float> ground_truth = {1.0f, 2.0f, 3.0f};ASSERT_THAT(float_array, Pointwise(NearWithPrecision(1e-4), ground_truth));
}

总结

主要介绍了Google Test和Google Mock中的基本概念,包括断言的形式、fixture等。
举例说明常用的断言命令要怎么写,包括浮点型、容器等。最后动手自己实现匹配器,满足复杂的测试需求

由于时间和篇幅的原因,本文没有介绍关于Mock的部分,这部分内容我会在下一个文章中介绍给大家

参考

  • GTest入门
  • Define a Mock Class

快速上手Google C++ 测试框架googletest相关推荐

  1. 「高效程序员的修炼」快速上手python主流测试框架pytest以及单元测试编写

    如果对你有帮助,就点个赞吧~ 本文主要介绍如果编写Python的单元测试,包括如何使用断言,如何考虑测试哪些情况,如何避免外部依赖对测试的影响,如果用数据驱动的方式简化重复测试的编写等等等等 文章目录 ...

  2. BG-UI,一个可以快速上手的后台UI框架

    BG-UI,一个可以快速上手的后台UI框架 1.简述 此项目为后台UI框架,并根据url中的hash提供简单的路由功能,页面的数据渲染依然交给服务器端. UI基于BootStrap3.兼容移动端.兼容 ...

  3. Google Test测试框架使用(Linux平台)

    文章目录 一.googleTest测试框架的基本介绍 1.基本概念 2.断言 3.基本断言判断 4.二元比较运算符 5.字符串比较 二.实际搭建google test测试框架 1.准备gtest框架 ...

  4. 快速上手Flask(一) 认识框架Flask、项目结构、开发环境

    文章目录 快速上手Flask(一) 认识框架Flask.项目结构.开发环境 Web开发轻量级框架Flask Flash历史和团队 Pallets 项目 flask运行过程 使用flask的场景 使用P ...

  5. 项目gtest测试框架 - GoogleTest(十)

    精简版本的C++单元测试框架 ,通过编写这个简单的测试框架,将有助于我们理解gtest. 1. 目录 类型 文件 说明 文件 ./CMakeLists.txt 整体项目工程文件 目录 ./debian ...

  6. Windows下安装谷歌测试框架Googletest并测试小例子

    一.下载 https://github.com/google/googletest/tree/master 下载并解压 新建一个build文件夹用于生成工程文件. 二.cmake 也可以使用cmake ...

  7. [BlockChain]比特币交易快速上手(基于测试链)

    文章目录 前言 安装钱包客户端Electrum 通过水龙头获取比特币 查看交易细节 在客户端查看交易 比特币的货币单位及换算关系 前言 简单的比特币收款交易的主要步骤主要有: 下载钱包客户端 获取收款 ...

  8. C++船长免费课程 Google测试框架实现

    一:预备知识 1. cout的本质:cout直接输出对象虚重载左移运算符,返回值和传参为左值引用和const引用,友元函数才能访问对象 代码规范其实是不允许直接using namespace的应该具体 ...

  9. 测试工程师如何快速上手新工作

    测试工程师如何快速上手新工作 测试工程师换到一份新工作,如何快速进入状态呢,自己最近换了新工作,踩过了很多坑才度过了适应期.为此分享一些自己的心得,供大家借鉴,主要有以下几点: 1.在新工作的第一天, ...

最新文章

  1. java更新blob字段的值_对一个BLOB字段如何用update 来更新?? (100分)
  2. 基于.NET实现数据挖掘--朴素贝叶斯算法
  3. Linux 最常用命令(简单易学,但能解决 95% 以上的问题)
  4. Struts2与Spring、Hibernate三者整合的过程示例
  5. koa中间件机制详解
  6. hibernate 使用别名查询
  7. OpenAI发布CLIP模型快一年了,盘点那些CLIP相关让人印象深刻的工作
  8. clob oracle 连接_Oracle clob 操作
  9. 最值钱无人车团队组织架构曝光:Waymo总共不到千人,2/3是工程师
  10. 在windows7家庭版安装软件时可能存在的问题
  11. What's the difference between forever and for good?
  12. nginx源码包编译安装
  13. 【论文笔记】NLP 预训练模型综述
  14. 恒讯科技分享:rust服务器搭建教程
  15. zblog php的foot模板在那里,zblog主题模板修改教程 zblog模板怎么修改?
  16. html5吹气球游戏,吹气球的游戏作文
  17. Lemon tree 柠檬树
  18. win10左右声道音量不一致的解决方法
  19. vm装linux不能上网 系统,VM 安装Linux后,以前的系统无法上网
  20. python实现名片管理器

热门文章

  1. pe系统如何读取手机_图文详解怎么用pe重做系统
  2. 【项目调研+论文阅读】Lattice LSTM神经网络医学文本命名实体识别 | day7
  3. 5_数据分析—数据可视化
  4. mac scp工具_Mac远程ssh连接乌班图并实现爬虫操作
  5. sas 文件传输 本地 服务器,SLIM SAS SFF-8654服务器转换线让传输更容易!
  6. 备份数据库的expdp语句_银行业Oracle RAC数据库迁移经验分享
  7. python 支持什么操作系统_操作系统1:什么是操作系统?
  8. MongoDB入门示例及介绍
  9. Spring Boot基础学习笔记09:Thymeleaf模板引擎
  10. 大数据学习笔记43:Hive - JDBC编程