本文主要参考谷歌的原文档和上面的一篇博客,主要对其内用进行了提取并加上了自己的理解,如想要更深的了解,请参考上面的资料。
如果想要使用Google Test,首先要学会编写断言。
断言就是那些用来检查一个条件是否为true的语句。一个断言可以有三种结果,分别是成功(Success)、非致命失败(nonfatal failure)和致命失败(fatal failure)。当一个致命失败发生时,它将终止对应的测试用例的执行;不是致命失败,则程序会继续执行下去。
对于一个测试点,我们有两类断言可以对它进行测试。不同的断言会对不同的用例有不同的影响。
ASSER_*这种断言会在断言失败时产生致命失败,并且终止当前测试用例。(由于这种断言在失败会立即返回,所以可能会跳过后边代码中清理(clean-up)代码,这可能会导致内存泄露)
EXPECT_*这种断言在失败时会造成非致命失败,这将不会终止当前测试用例。(这种断言出错后不会终止当前测试用例,而是继续执行。若后续的断言语句依然有不通过的,则后续的断言依然可以报出测试失败信息)
如果需要自定义失败消息,可以采用如下方式:
ASSERT_EQ(x.size(), y.size()) << "自定义的消息";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "自定义的消息" << i;
}
说白了就是使用<<运算符或多个该运算符序列将自定义的消息流式传输到宏中。
任何可以流式传入ostream的都可以流式传入断言宏。尤其是C字符串和string对象。如果一个宽字符串(wchar_t*,TCHAR*在Windowss上的UNICODE模式,或模式std::wstring)流式传输到一个断言,在打印时将被转换为UTF-8格式)。
断言分为三种分别为:基本断言、二元比较断言、字符串比较断言。
基本断言也就是最基本的逻辑运算。
致命断言 | 非致命断言 | 什么条件下通过 |
---|---|---|
ASSERT_TRUE(事件) | EXPECT_TRUE(事件) | 事件为真 |
ASSERT_FALSE(事件) | EXPECT_FALSE(事件) | 事件为假 |
注意点:
二元比较断言主要用于两个值之间的比较
致命断言 | 非致命断言 | 什么条件下通过 |
---|---|---|
ASSERT_EQ(value1,value2) | EXPECT_EQ(value1,value2) | value1==value2 |
ASSERT_NE(value1,value2) | EXPECT_NE(value1,value2) | value1!=value2 |
ASSERT_LT(value1,value2) | EXPECT_LT(value1,value2) | value1<value2 |
ASSERT_LE(value1,value2) | EXPECT_LE(value1,value2) | value1<=value2 |
ASSERT_GT(value1,value2) | EXPECT_GT(value1,value2) | value1>value2 |
ASSERT_GE(value1,value2) | EXPECT_GE(value1,value2) | value1>=value2 |
注意点:
该断言主要用于C字符串之间的比较,string对象可以使用EXPECT_EQ,EXPECT_NE等进行比较。
致命断言 | 非致命断言 | 什么条件下通过 |
---|---|---|
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) | 字符串内容不同(忽略大小写) |
注意点:
下面通过代码的方式进行实践。
TEST(TestSuiteName, TestName) {
/*在这里书写代码*/
}
TEST()的参数:
第一个参数是测试集的名称,第二个参数是测试用例的名称,该测试用例属于第一个参数所指定的测试集。
这两个名称都必须是合法的C++标识符,并且不应该包含下划线(_)。
一个测试用例的完整名称由包含它的测试集和它自己的名称组成。不同测试集里的测试用例可以使用同样的名称。
现在声明一个简单的函数,并给出其测试集
int Factorial(int n); //仅作为一个例子,假设该函数能够计算n的阶乘
// 测试0的阶乘
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1); //如果出错,这种断言会产生非致命失败
}
// 测试正数的阶乘
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
注意点:
Google Test通过测试集来组织这些测试的结果,所以逻辑相关的测试用例应该被放在相同的测试集中(也就是说,逻辑相关的测试用例,TEST()的第一个参数应该是一样的)
在命名测试集和测试用例时,应该遵循和命名函数和类相同的规范。
在实际测试的过程中,如果写了多个测试用例,且他们都需要用到相似的数据,可以使用Test Fixtures。
Test Fixtures允许给不同的测试用例重用一些相同的配置。
使用Fixtures时,使用TEST_F()替代TEST(),因为TEST_F()可以使你访问到fixture中的对象和子程序。
TEST_F(TestFixtureName, TestName) {
/*在这里书写代码*/
}
注意点:
Google Test会在运行时,为每一个使用TEST_F()定义的测试用例创建一个新的fixture对象,立即调用SetUp()进行初始化,然后运行测试用例,之后会通过调用TearDown()来清理资源,最后删除fixture对象。需要注意的是,同一个测试集中不同的测试用例使用的是不同的fixture对象,Google Test总是在为下一个测试用例创建fixture之前,删除当前测试使用后的fixture。Google Test不会给多个测试用例重用同一个fixture对象。一个测试用例对fixture的任何操作不会影响到其他测试用例。
下面做一个实例
/*使用fixture测试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的类创建一个名为FooTest的fixture类。
class QueueTest : public ::testing::Test { //从::testing::Test派生一个类 QueueTest
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
类声明完毕后,测试用例如下:
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);
n = q1_.Dequeue();
ASSERT_NE(n, nullptr); //使用ASSERT的原因在于下面的代码需要对指针n解引用,这将会在n是NULL的时候导致段错误
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;
n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}
当这些测试用例在运行时:
TEST()和TEST_F()隐式地把它们的测试注册给 googletest,所以,不像许多其他C++测试框架,你不必为了运行它们而一一列举它们。
在你定义完测试用例之后,你可以使用RUN_ALL_TESTS()运行它们,当所有测试成功时返回0,否则返回1。注意RUN_ALL_TESTS()运行你链接的所有测试——它们可以来自不同的测试集,甚至是不同的源文件。
当RUN_ALL_TESTS()被调用时:
当在执行某一步时发生了致命错误,下面的所有步骤都会被跳过
大多数用户不需要编写main函数,只要链接gtest_main库就可以,该库提供了合适的入口点。当你想在测试用例执行之前执行一些自定义的操作,但这些操作又不能通过现有的框架去添加(如fixture),就需要编写main函数了。
你编写的main函数需要返回RUN_ALL_TESTS()的返回值。
#include "this/package/foo.h"
#include "gtest/gtest.h"
namespace my {
namespace project {
namespace {
// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
protected:
// You can remove any or all of the following functions if their bodies would
// be 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 constr 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).
}
// Class members 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), 0);
}
// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
}
} // namespace
} // namespace project
} // namespace my
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
::testing::InitGoogleTest()函数解析 googletest 标记的命令行参数,并移除所有已识别的标记。这允许用户通过不同的标记控制测试程序的行为。但必须在调用RUN_ALL_TESTS()之前调用这个函数,否则标记将无法得到适当的初始化。AdvancedGuide(高级教程)中有相关的描述。
在Windows下,InitGoogleTest()同样也可以基于宽字符串使用,因此它也可以被用于以UNICODE模式编译的程序。
单纯的编写一个main函数过于麻烦。这也是Google Test 提供了一个基础的main函数实现的原因。如果它能够满足你的需求的话,仅需要将你的测试用例和库gtest_main链接就可以了。