GoogleTest测试C++代码
GoogleTest测试框架基本使用方法:
首先,给出官方文档的链接。以下内容主要是我自己翻译的官方文档并结合自己的理解,总结出的基本要点。如果想要对技术更深入的了解,建议还是去看官方文档。最后说明一下,这里的内容仅仅在Linux系统的上执行过,其他的系统应该也是按照类似的步骤进行。Linux下关于如何安装GoogleTest框架,请参考我在CSDN的这篇博客
基本概念
使用GoogleTest要先从学习写断言开始,断言用于检测一个测试条件是否为真。断言的结果有三种情况:success, nonfatal failure, fatal failure。如果 fatal failure出现,它将会打断当前的函数;否则程序会正常运行。
一个测试实例可以包含多个测试,我们需要把这些测试组织成合理的结构。当多个测试实例需要共享公共对象和或者子程序,我们可以把他们组织到一个测试类中。
断言
GoogleTest的断言是一种类似于函数调用的断言机制。我们可以在GoogleTest本身的断言消息后面定义自己的测试失败信息。下面说明几种不同断言方式:
ASSERT_*
产生fatal failures
,直接终止当前函数的运行EXPECT_*
: 产生nonfatal failures
,不会终止当前函数运行EXPECT_*
: 最常用的一种方式,可以允许报告产生一个或者多个failer
为了提供自定义的失败信息,可以使用C++的stream流操作把字符输入到断言中,借助于<<
操作符号。
比如:
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;
}
注意,任何可以写入流ostream
的,都可被写入断言宏,比如C的字符串、string对象,甚至可以是广义上的字符(流):Windows的Unicode下的wchar_t*、 TCHAR*或者C++的std::string
。所有流的输入都会转化成UTF-8
的格式。
基础断言判定:
Name | Academy | score |
---|---|---|
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 |
如果上述的一个测试失败,那么会打印出val1
和val2
的值。
Value参数必须与断言比较运算符兼容,否则有编译错误。这些断言机制可以使用用户自定义的结构,但是必须进行运算符重载 。如果使用了自定义结构,我们最好使用ASSERT_*()
,这样不仅会输出比较结果,而且会输出操作数。
ASSERT_EQ()
会进行指针比较, 如果使用C风格字符串,那么比较的是地址!!如果要比较值的话,使用ASSERT_STREQ()
, 如果判断C字符串是否是NULL
,使用ASSERT_STREQ(NULL, c_string)
。 如果比较string
,那么使用ASSERT_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 content |
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 content, ignoring case |
简单的测试实例:
创建一个test:
- 使用
TEST()
宏定义和命名一个特是函数,这是一个普通的无返回值的C++函数。 - 在函数内部,可以包含任何我们想要添加的C++条件,使用GoogleTest断言去检查相关的值。
- 测试的结果取决于内部的断言机制。如果有任何的测试失败(不管是fatally还是non-fatally),或者测试中断,那么整个测试失败;否则测试成功。
TEST(testCaseName, testName) {... test body ...
}
testCaseName
是 test case的名字,testName是test case内部测试的名称。两者的名称必须是合法的C++标识符,不允许有下划线( _ ) 。不同的test case的内部测试可以有相同的独立的名字。
int Factorial(int n); // Returns the factorial of n// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {EXPECT_EQ(1, Factorial(0));
}// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {EXPECT_EQ(1, Factorial(1));EXPECT_EQ(2, Factorial(2));EXPECT_EQ(6, Factorial(3));EXPECT_EQ(40320, Factorial(8));
}
GoogleTest通过test case组织测试结果,因此逻辑相关的测试必须在一个test case中;换句话说,TEST()
的第一个参数必须相同。上面例子的HandlesZeroInput
和HandlesPositiveInput
都属于FactorialTest
这个测试实例。
Test Fixtures: 对不同的数据使用相同的测试数据配置
Test Fixture允许我们使用相同对象配置进行不同的测试。
具体步骤:
1. 从::testing::Tes
派生一个类。使用是public:
或者protected:
,因为我们想要在子类中获取fixture members
2. 在派生类的内部,声明我们想使用的任何对象
3. 如果有必要, 通过使用默认构造函数或者SetUp()
函数为每个测试准备测试对象。
4. 如果有必要, 使用一个析构函数或者TearDown()
函数来释放构造函数或者SetUp()
所申请的资源。 想要理解在什么时候使用SetUp()
和TearDown()
函数, 阅读这篇文章.
5. 如果必要的话,可以定义子程序,让测试之间共享。
当使用一个fixture时,应该使用TEST_F()
而不是TEST()
,因为前者会让我们获取一个test fixture的对象或者子程序。比如:
TEST_F(test_case_name, test_name) {... test body ...
}
与TEST()
类似,第一个参数是test case的名字。但是第二参数必须是test fixture类的名字。
C++的宏系统不允许我们创建一个单独的宏来处理各种类型的test。这样做会有编译错误。
对于每个使用TEST_F()
定义的测试,Google Test会:
- 在运行期间创建一个新的test fixture
- 通过
SetUp()
立刻进行初始化 - 运行test
- 通过
TearDown()
函数清理 - 删除这个test fixture。注意:一个test中的不同test拥有不同的test fixture对象,Google Test总是在创建下一个test fixture之前删除当前的test fixture。对于多个test,Google Test不会重复使用相同的test fixture。一个test的任何更改都不会影响其他的test。
比如,现在编写一个FIFO队列的测试,队列的实现方式:
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类。假设该类的名称为Foo
, 按照惯例,我们应该给fixture命名为FooTest
class QueueTest : public ::testing::Test {protected:virtual void SetUp() {q1_.Enqueue(1);q2_.Enqueue(2);q2_.Enqueue(3);}// virtual void TearDown() {}Queue<int> q0_;Queue<int> q1_;Queue<int> q2_;
};
本例子中,没用TearDown()
函数,因为没有资源需要释放。
现在 ,使用TEST_F()
测试这个fixture:
// 用于测试是否是空队列
TEST_F(QueueTest, IsEmptyInitially) {EXPECT_EQ(0, q0_.size());
}// 测试出队的工作状态
TEST_F(QueueTest, DequeueWorks) {int* n = q0_.Dequeue();EXPECT_EQ(NULL, n); // 判断相等的情况n = q1_.Dequeue();ASSERT_TRUE(n != NULL);EXPECT_EQ(1, *n);EXPECT_EQ(0, q1_.size());delete n;n = q2_.Dequeue();ASSERT_TRUE(n != NULL);EXPECT_EQ(2, *n);EXPECT_EQ(1, q2_.size());delete n;
}
ASSERT_
与EXPECT_
的区别在前面的文章提到了,这里不在赘述。
以上面的例子,说明GoogleTest的测试步骤:
- Google Test创建一个
QueueTest
对象, 我们称之为t1
t1.SetUp()
初始化t1
。t1
进行第一个测试IsEmptyInitially
测试完成后
,t1.TearDown()
清理所有的资源。t1
析构。- 上述的步骤重复的在另一个
QueueTest
对象上执行。(DequeueWorks
开始执行上述步骤)
建立一个测试:
TEST()
与TEST_F()
会跟随Google自动进行注册,如果想要执行,我们不需要重新列出所有定义的测试。
在定义测试之后,使用RUN_ALL_TEST()
。如果测试成功则返回0,否则返回0.,执行这个语句的时候,所有的链接单元都会被测试,它们可以是不同的test case的。
一般步骤:
- 保存所有Google Test标志的状态。
- 为第一个测试创建一个test feature对象。
- 通过
SetUp()
函数初始化test - fixture test开始在该对象上执行
- 测试结束后,通过
TearDown()
释放资源 - 删除fixture
- 恢复Google Test的所有标志状态
- 其他的test重复地执行上述步骤。
注意,如果构造函数在第2步产生了fatal failure,那么3-5步会自动跳过。同样的,3产生了fatal failure,第4步跳过。
注意: 我们必须返回RUN_ALL_TEST()
的值,否则gcc
会给出编译错误。也就是说,主函数必须返回RUN_ALL_TEST()
的值!而且RUN_ALL_TEST()
只能执行一次!!
编写主函数:
Google Test的官方文档给出了一个测试模板:
#include "this/package/foo.h"
#include "gtest/gtest.h"namespace {// The fixture for testing class Foo.
class FooTest : public ::testing::Test {protected:// You can remove any or all of the following functions if its body// is empty.FooTest() {// You can do set-up work for each test here.}virtual ~FooTest() {// 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:virtual void SetUp() {// Code here will be called immediately after the constructor (right// before each test).}virtual void TearDown() {// Code here will be called immediately after each test (right// before the destructor).}// Objects declared here can be used by all tests in the test case for Foo.
};// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {const string input_filepath = "this/package/testdata/myinputfile.dat";const string output_filepath = "this/package/testdata/myoutputfile.dat";Foo f;EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {// Exercises the Xyz feature of Foo.
}} // namespaceint main(int argc, char **argv) {::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
::testing::InitGoogleTest()
用于解析Google Test flags的命令行,并且一处所有已经被识别的标志,具体的用法请参照这个文档
Visual C++用户须知!!
因为本人不用vc++,因此各位用VC的dalao就自己看官方文档吧。。。。。。
测试代码
以最大子数组线性时间求和问题为例,介绍GoogleTest的测试框架。给出假期刷题时PAT1007原题目链接,并附上AC的代码,算法的原理就不在赘述了:
#include <bits/stdc++.h>
using namespace std;
int num[10005], N;int main() {cin >> N;bool flag = true;for(int i = 0; i < N; ++i) {cin >> num[i];if(num[i] >= 0) {flag = false;}}// 注意sum初始化要小于0int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0;while(tmp_b < N) {tmp_sum += num[tmp_b];if(tmp_sum > sum) { // 更替区间范围sum = tmp_sum;a = tmp_a;b = tmp_b;}if(tmp_sum < 0) { // 重新开始起点tmp_sum = 0;tmp_a = tmp_b + 1;}++tmp_b;}if(flag) {cout << 0 << " " << num[0] << " " << num[N - 1];} else {cout << sum << " " << num[a] << " " << num[b];}return 0;
}
但是,上述代码不好直接测试,因此,把核心功能分离出来写成函数,同时自定义结构体作为函数的返回值。改进后的代码如下:
实际可能出现的情况有下面几种:
1. 全是负数:左右标记分别是区间范围,子数组之和输出0
2. 有最大子数组,且最大子数组唯一:输出最大子数组的范围和所有元素之和
3. 有多个最大子数组:只输出第一个最大子数组,格式同2
#include <gtest/gtest.h>class Node {public:Node(): sum(0), l(0), r(0) {} // 初始化int sum, l, r; // 区间和、左右范围,从0开始
};Node maxFun(int arr[], int N, bool flag) {Node node;int a = 0, b = 0, sum = -1, tmp_sum = 0, tmp_a = 0, tmp_b = 0;while(tmp_b < N) {tmp_sum += arr[tmp_b];if(tmp_sum > sum) { // 更替区间范围sum = tmp_sum;a = tmp_a;b = tmp_b;}if(tmp_sum < 0) { // 重新开始起点tmp_sum = 0;tmp_a = tmp_b + 1;}++tmp_b;}if(flag) {node.l = 0;node.r = 9;node.sum = 0;} else {node.l = a;node.r = b;node.sum = sum;}return node;
}// 需要全局重载
bool operator==(Node a, Node b) {return a.sum == b.sum && a.l == b.l && a.r == b.r;
}// 全是负数的测试情况
int num1[10] = { -1, -2, -5, -2, -8, -6, -9, -3, -10, -4};
// 只有一个最大子数组,左右范围是2 7,和是25
int num2[10] = {1, -14, 5, 6, 8, 3, -1, 4, -10, 4};
// 有多个,在这里用两个测试,左右范围应该是2 4 和是19
int num3[10] = {1, -14, 5, 6, 8, -300, 5, 6, 8, -4};TEST(MYTEST, IsOk) {Node n1, n2, n3;n1.l = 0;n1.r = 9;n1.sum = 0;n2.l = 2;n2.r = 7;n2.sum = 25;n3.l = 2;n3.r = 4;n3.sum = 19;ASSERT_EQ(n1, maxFun(num1, 10, true));ASSERT_EQ(n2, maxFun(num2, 10, false));ASSERT_EQ(n3, maxFun(num3, 10, false));
}int main(int argc, char **argv) {::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
由于是自定义的返回值,所以根据官方文档的建议,使用ASSERT_*
。测试通过结果如下:
假设更改n3
的l
属性为4,那么测试失败的结果如下:
其中,有相应的不匹配提示。由于本测试比较简单,所以没有使用到test fixture的技术。如果需要,直接套用模板即可。更高级的功能可以参考
一些感想:
本次测试仅通过官方文档进行学习。官方的参考文档是最佳的参考资料。尤其是对于我们不熟悉的技术领域,更应该通过阅读有关文档进行学习。英文应该作为技术开发人员的一项基本能力,不仅仅是为了考研或者是所谓的四六级。很多最新的资料或者比较高端的技术或者一些停机期刊的论文等,几乎没有中文版的,因此我们更应该不断提高自己的英文水平。同时,要怀着积极的心态去拥抱新的技术和变化,善于利用工具提高开发或者测试效率。
GoogleTest测试C++代码相关推荐
- Ubuntu安装GoogleTest框架并测试C++代码
GoogleTest框架测试C++代码 开发环境:Ubuntu16.04 判断是否安装cmake 输入cmake -v,如果没有安装,输入sudo apt-get install cmake 打开终端 ...
- 如何编写可测试的代码 哈利勒的方法论
Understanding how to write testable code is one of the biggest frustrations I had when I finished sc ...
- xampp测试php代码,php用xampp测试
如果只是简单的测试的话,可以用echo或者var_dump打印信息来查看,当然如果你用的是zend或者eclipse这样的编辑器的话,可以加上zendbug来调试信息 php如何进行单元测试 何为单元 ...
- .NET Core TDD 前传: 编写易于测试的代码 -- 全局状态
第1篇: 讲述了如何创造"缝". "缝"(seam)是需要知道的概念. 第2篇, 避免在构建对象时写出不易测试的代码. 第3篇, 依赖项和迪米特法则. 本文是 ...
- 单元测试:如何编写可测试的代码及其重要性
原文来自互联网,由长沙DotNET技术社区编译.如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除.限于译者的能力有限,个别语句翻译略显生硬,还请见谅. 作者:谢尔盖·科洛迪(SERGEY ...
- .NET Core TDD 前传: 编写易于测试的代码 -- 依赖项
第1篇: 讲述了如何创造"缝". "缝"(seam)是需要知道的概念. 第2篇, 避免在构建对象时写出不易测试的代码. 本文是第3篇, 讲述依赖项和迪米特法则 ...
- .NET Core TDD 前传: 编写易于测试的代码 -- 构建对象
该系列第1篇: 讲述了如何创造"缝". "缝"(seam)是需要知道的概念. 本文是第2篇, 介绍的是如何避免在构建对象时写出不易测试的代码. 本文的概念性内 ...
- .NET Core TDD前传: 编写易于测试的代码 -- 缝
有时候不是我们不想做单元测试, 而是这代码写的实在是没法测试.... 举个例子, 如果一辆汽车在产出后没完成测试, 那么没人敢去驾驶它. 代码也是一样的, 如果项目未能进行该做的测试, 那么客户就不敢 ...
- 用Java测试多线程代码
测试多线程代码是一个艰巨的挑战. 尝试测试并发性时获得的第一个建议是尽可能地在代码中隔离并发问题. 这是一般的设计建议,但在这种情况下甚至更重要. 确保首先正确地对并发构造所包装的逻辑进行单元测试. ...
最新文章
- 2021年,Java开发者值得学习的13项技能
- angular2 如何使用websocket
- php 打印错误 display,php错误display及error_reporting的使用
- Check Point CEO:“我们正在积极寻找收购目标”
- 中文字串截取无乱码的问题
- membership.findusersbyname模糊匹配的写法
- C#基于websocket-sharp实现简易httpserver(封装)
- SSH框架下的表单重复提交
- idea在编辑界面上显示多个文件
- python网络爬虫从入门到精通吕云翔pdf_Python 网络爬虫从入门到精通
- 《别闹了,费曼先生》听书笔记
- 微信小程序开发者工具扫码成功但是进不去
- OceanBase 社区版 3.1.3 Docker镜像尝鲜,能错过?不存在的
- 各种颜色十六进制代码表
- Browsing HDFS报错
- Android HIDL 介绍学习之客户端调用
- GIT创建版本库及版本的迭代
- QT5.7操作word
- 禁用和启用input元素
- js 给元素添加自定义属性
热门文章
- 解刨一台计算机,解剖一台计算机.doc
- 数据结构 1-0 绪论
- 【caffe-windows】 caffe-master 之Matlab中model的分类应用
- Python中中文字符也算单个字符
- MATALB中的最大和最小常量
- tensorflow没有代码提示的问题
- 树莓派教程 - 1.6 树莓派GPIO库wiringPi 外接USB串口ttyUSB ch340 cp2102
- Node.js 初步学习总结
- CentOS 5.5 安装配置全攻略 (无线上网 更新源 显卡驱动 firefox3.6 flash插件 编译boost1.43.0 雅黑字体...
- .Net 获取IP 地址和计算机名(本地网)