gtest入门
介绍
gtest是谷歌开发的用来做C++单元测试的测试框架
基本概念
使用gtest,你就需要写断言(assertions),用来检查一个表达式是否为true。断言的结果有三个:正确、非致命错误、致命错误。如果出现致命错误,就会退出当前函数,否则继续执行当前函数的后续部分。
测试用例(tests)使用断言来核实被测试代码的行为。
测试组件(suits)可以包含一个或多个测试用例。通过把测试用例分组到不同的测试组件,可以展现测试代码的结构。如果同一个测试组件中的测试用例需要共享某些对象,你可以把它们放到一个 fixture 类中。
一个测试程序,可以包含多个测试组件。
断言
gtest的断言就像函数调用一样是一些宏。通过断言一个类或者函数的表现来测试它们。如果一个断言失败了,gtest会打印出失败的源文件和行号,连同失败的信息。你可以定义失败信息,这会被附加到gtest的输出信息上。
ASSERT_* 版本的断言失败的时候生成致命错误,并退出当前函数。EXPECT_* 版本的断言生成非致命错误,不会退出当前函数。通常来说,更推荐EXPECT_*宏,因为它们可以在一个用例中报告出更多的失败测试。不过,如果断言失败时,继续执行下去没有意义,你就应该使用ASERT_* 断言。
因为测试用例失败的时候,ASSERT_* 断言会立刻从函数中退出,所以,可能这样会跳过资源清理的代码,这或许会导致内存泄漏。
为了提供一个定制的错误信息,仅仅使用重定向操作符到断言的宏就可以了:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; for (int i = ; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
任何能被重定向到 std::ostream 的对象,都可以被重定向到断言的宏,包括 C风格的字符串或者 std::string 对象。如果一个宽字节的字符串重定向到了断言,打印输出的时候,它会被转为UTF-8格式。
基本的断言
true/false 条件判断
Fatal assertion | Nonfatal assertion | Verifies |
ASSERT_TRUE(condition) | EXPECT_TRUE(condition) | condition is true |
ASSERT_FALSE(condition) | EXPECT_FALSE(condition) | condition is false |
二进制比较
这一块是比较两个值的断言
Fatal assertion | Nonfatal assertion | Verifier |
ASSERT_EQ(v1, v2) | EXPECT_EQ(v1, v2) | v1 == v2 |
ASSERT_NE(v1, v2) | EXPECT_NE(v1, v2) | v1 != v2 |
ASSERT_LT(v1, v2) | EXPECT_LT(v1, v2) | v1 < v2 |
ASSERT_GT(v1, v2) | EXPECT_GT(v1, v2) | v1 > v2 |
两个值必须是运算符可比的,否则会编译错误。如果重载了需要的运算符,这些断言可以用在用户自定义的类型上。
一般来说,相对于 ASSERT_TRUE(actual == expected),ASSERT_EQ(actual, expected)更被推荐使用,因为失败的时候,它可以告诉你两个参数的值。
参数只被评判一次,因此参数可以有副作用。不过,参数的顺序是未定义的,所以测试代码不应该依赖其它的测试。
ASSERT_EQ指的是指针相等,如果判断C语言风格字符串,它判断的是内存位置是否相等,不是是否有相同的值。如果你想比较 const char* 类型的字符串,使用ASSERT_STREQ。判断字符串是否为NULL,使用ASSERT_STREQ(str, NULL)。比较两个 std::string 对象,应该使用ASSERT_EQ。
对于指针比较,使用 *_EQ(ptr, nullptr)和 *_NE(ptr, nullptr)
对于浮点型数值的比较,看advanced文档
字符串比较
比较C风格的字符串
Fatal assertion | Nonfatal assertion | Verifier |
ASSERT_STREQ(s1, s2) | EXPECT_STREQ(s1, s2) | 字符串内容相同 |
ASSERT_STRNE(s1, s2) | EXPECT_STRNE(s1, s2) | 字符串内容不同 |
ASSERT_STRCASEEQ(s1, s2) | EXPECT_STRCASEEQ(s1, s2) | 忽略大小写,字符串内容相同 |
ASSERT_STRCASENE(s1, s2) | EXPECT_STRCASENE(s1, s2) | 忽略大小写,字符串内容不同 |
CASE意味着忽略大小写。NULL 和空字符串("")是不同的
STREQ和STRNE也接受宽字节字符串
简单的测试例子
创建一个测试步骤
- 使用 TEST() 宏定义并命名一个测试函数。
- 在这个函数中,可以放入任何有效的C++表达式,并使用gtest断言检查值
- 测试结果被断言来判断。如果任何断言失败,测试就失败。
TEST(TestSuiteName, TestName) {
... test body ...
}
TEST() 宏的第一个参数为测试组件的名字,第二个参数为用例名字。两个名字都必须是有效的C++标识符,不能包含下划线。一个测试的全名包含测试组件和用例名字。不同的测试组件里的用例名字可以相同。
比如,一个简单的整形函数:
int Factorial(int n); // Returns the factorial of n
该函数的测试组件可以如下:
// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(), );
} // Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(), );
EXPECT_EQ(Factorial(), );
EXPECT_EQ(Factorial(), );
EXPECT_EQ(Factorial(), );
}
gtest通过组件来分组测试结果。所以,逻辑相关的测试应该划分到相同的组件,也就是,第一个参数应该相同。上面的例子,我们有两个测试用例,HandlesZeroInput 和 HandlesPositiveInput 都属于组件 FactorialTest
测试设备:多个用例使用同样的数据
如果你写了多个用例,测试同样的数据,你应该使用 fixture。这可以让你重用相同的配置。
创建一个 fixture 步骤:
- 写一个类继承 testing::Test。使用 protected: 开始内容,因为我们从子类中访问类的成员
- 在类中声明任何你打算使用的成员
- 如果有必要,写一个默认构造函数或者SetUp() 函数为每个用例准备数据。常见的错误是把 SetUp 拼写成 Setup,这可以通过关键字 override 来避免这个问题
- 如果有必要,写一个析构函数或者 TearDown() 函数来释放SetUp中申请的资源。在faq.md页面可以看到应该使用 构造/析构 还是 SetUp/TearDown
- 如果需要的话,为你的测试定义一个子程序来实现复用。
当使用 fixture时,使用TEST_F() 而不是 TEST():
TEST_F(TestFixtureName, TestName) {
... test body ...
}
TEST_F(),第一个参数为fixture类的名字,_F的意思是: fixture
当然,使用 TEST_F() 之前,你需要定义一个 fixture 类,否则会编译错误
对于每一个使用 TEST_F() 的测试用例,gtest 会在运行时创建一个全新的 fixture 对象,并立刻通过 SetUp 初始化,运行测试,通过 TearDown() 清理资源,然后析构这个对象。相同 suit 的不同测试用例使用不同的 fixture 对象,并且在创建一个新的 fixture 对象前,gtest 会删除老的 fixture 对象。fixture 对象不会被重用。
作为一个例子,我们为 FIFO 的 Queue 写一个用例,如下接口:
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 类:
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue();
q2_.Enqueue();
q2_.Enqueue();
} // void TearDown() override {} Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
TearDown 不是必须的,因为我们不需要在每个用例之后清理Queue。
现在,写一个测试组件:
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), );
} TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr); n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, );
EXPECT_EQ(q1_.size(), );
delete n; n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, );
EXPECT_EQ(q2_.size(), );
delete n;
}
上面的例子中,我们既使用了 ASSERT_*,也使用了 EXPECT_*。使用 EXPECT_* 是因为我们想让测试函数运行下去,即使某些用例失败了。使用 ASSERT_* 是因为某个用例失败后,继续运行下去,就没有意义了。比如如果某个指针为 nullptr,不能再判断该指针指向的值了。
当这个测试运行的时候,发生了下面的事情:
- 构造一个 QueueTest 对象(t1)
- t1.SetUp() 初始化 t1
- 第一个用例 IsEmptyInitially 运行
- t1.TearDown 执行清理工作
- t1 被析构
- 上面的步骤被重复执行,这次创建一个 t1 的对象,用来运行 DequeueWorks 用例
调用测试用例
TEST() 和 TEST_F() 只是注册了测试用例。所以你不需要为了运行它们,重新列出你定义的测试。
定义用例之后,你需要使用 RUN_ALL_TESTS() 来运行它们,这个宏会返回 0 如果所有的用例都是成功的,否则就是1。RUN_ALL_TESTS() 会运行所有的用例,即使是不同的组件,甚至不同的源文件。
RUN_ALL_TESTS()做了如下事情:
- 保存所有 flags 的状态
- 为第一个测试创建 fixture 对象
- 通过 SetUp 初始化
- 运行 fixture 对象的用例
- 使用 TearDown 做清理工作
- 析构 fixture 对象
- 恢复所有的 flags 状态
- 为下一个测试重复上面的步骤,知道所有用例结束
注意:你不能忽略 RUN_ALL_TESTS() 的返回值,否则会编译错误。这么设计的原因是:自动测试根据退出码来决定是否一个测试通过了,而不是在控制台的输出。同时,你只能调用 RUN_ALL_TESTS() 一次
写main函数
写自己的main函数,这里应该返回 RUN_ALL_TESTS()
样板如下:
#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.
} ~FooTest() override {
// 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: void SetUp() override {
// Code here will be called immediately after the constructor (right
// before each test).
} void TearDown() override {
// 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 suite for Foo.
}; // Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
const std::string input_filepath = "this/package/testdata/myinputfile.dat";
const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(f.Bar(input_filepath, output_filepath), );
} // Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
} } // namespace int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
testing::InitGoogleTest() 解析命令行参数,并且移除所有已识别的标志。
局限性
gtest 是线程安全的,但是这个线程安全仅仅在支持 pthread 的系统的可以。在其他系统中使用两个线程运行测试是不安全的,比如 windows。
gtest入门的更多相关文章
- gtest入门简介
Gtest测试,入门简介: 资源:http://developer.51cto.com/art/201108/285290.htm http://www.cnblogs.com/bangerlee/a ...
- [单元測试]_[VC2010使用gtest单元測试入门]
场景: 1. gtest作为C++的单元測试工具非常优秀了,它集成了非常多标准assert所没有的功能,比方让流程继续运行的EXPECT,仅仅測试特定測试用例的--gtest_filter, 输出xm ...
- Google C++测试框架系列入门篇:第一章 介绍:为什么使用GTest?
原始链接:Introduction: Why Google C++ Testing Framework? 词汇表 版本号:v_0.1 介绍:为什么使用GTest? GTest帮助你写更好的C++测试代 ...
- [软件测试]Linux环境中简单清爽的Google Test (GTest)测试环境搭建(初级使用)
本文将介绍单元测试工具google test(GTEST)在linux操作系统中测试环境的搭建方法.本文属于google test使用的基础教程.在linux中使用google test之前,需要对如 ...
- Google C++测试框架系列:入门
Google C++测试框架系列:入门 原始链接:V1_6_Primer 注 GTest或者Google Test: Google的C++测试框架. Test Fixtures: 这个词实在找不到对应 ...
- GoogleTest入门
Googletest入门 来源:https://github.com/google/googletest/blob/master/googletest/docs/primer.md P.S. gmoc ...
- gtest 安装与使用
打开资源管理器: nautilus . gtest 获取 从:https://www.bogotobogo.com/cplusplus/google_unit_test_gtest.php 获取gte ...
- gtest日志在工程项目中的应用
网上有各种gtest的入门教学,这里就不一一重复了.本文的目的是讲解如何将gtest应用于工程应用中.利用测试驱动开发这样的理论,来先写测试代码,当自动化测试跑通以后,主工程的代码也就编写完了. 这里 ...
- Linux下Google Test (GTest)测试环境搭建步骤
1.下载GTEST 下载链接为:https://code.google.com/p/googletest/downloads/list 目前GTEST的最新版本为gtest-1.7.0.zip,因此我 ...
随机推荐
- vim命令(转)
1.Linux下创建文件 vi test.txt 或者 vim test.txt 或者 touch test.txt 2.vi/vim 使用 基本上 vi/vim 共分为三种模式,分别是命令模式(Co ...
- [RN] React Native 滚动跳转到指定位置
React Native 滚动跳转到指定位置 一.结构 <ScrollView horizontal={true} ref={(view) => { this.myScrollView = ...
- [RN] React Native 图片懒加载库 animated-lazy-image
React Native 图片懒加载库 animated-lazy-image 官方Github地址: https://github.com/danijelgrabez/lazy-image 使用效果 ...
- JavaScript语法-流程控制语句
一.JavaScript特殊语法 JS特殊语法: 1. 语句以;结尾,如果一行只有一条语句则 ;可以省略 (不建议) 2. 变量的定义使用var关键字,也可以不使用 * 用: 定义的变量是局部变量 * ...
- 洛谷P2949题解
若想要深入学习反悔贪心,传送门. Description: 有 \(n\) 项工作,每 \(i\) 项工作有一个截止时间 \(D_i\) ,完成每项工作可以得到利润 \(P_i\) ,求最大可以得到多 ...
- Elasticsearch与Solr优缺点比较
Elasticsearch简介 Elasticsearch是一个实时的分布式搜索和分析引擎.它可以帮助你用前所未有的速度去处理大规模数据. 它可以用于全文搜索,结构化搜索以及分析,也可以将这三者进行组 ...
- 站在BERT肩膀上的NLP新秀们(PART I)
站在BERT肩膀上的NLP新秀们(PART I)
- 在线生成安卓APP图标
移动应用图标/启动图生成工具,一键生成所有尺寸的应用图标/启动图 在线生成安卓APP图标生成 图标在 线 在线图标 安卓图标 生成图标 https://icon.wuruihong.com/ 在线pn ...
- SVN提示is already locked 解决办法
当svn提示is already locked ,反复clean up也无用, 可以在cmd下进入到目标文件夹的目录 执行svn cleanup 等待执行成功,就可以update了
- SpringBoot(十六):SpringBoot2.1.1集成fastjson,并使用fastjson替代默认的MappingJackson
springboot2.1.1默认采用的json converter是MappingJackson,通过调试springboot项目中代码可以确定这点.在springboot项目中定义WebMvcCo ...