Googletest入门文档翻译

Googletest入门


介绍:为什么选googletest?


googletest帮助你写出更好的C++测试程序

googletest是google的测试技术团队开发的一个测试框架,开发这个框架是出于google特定的需求和约束考虑。无论使用Linux,Windows,还是Mac,只要你写C++代码,googletest都能帮助你。并且它不仅支持单元测试,还支持其他类型的测试。
那么,什么样的测试是好的测试,googletest又是怎么做的?我们认为:

  1. 不同测试之间应该是独立的可重复的。如果一个测试的成功与否依赖于其他的测试结果,这将是一件痛苦的事。googletest将各个测试运行在不同的对象中,以此独立不同的测试。如果一个测试挂了,googletest能让你在孤立的环境中运行它,实现快速调试。
  2. 测试集应该组织良好,并且能反映被测代码的结构。googletest将相关的tests分组到test cases中,使之能够共享数据和子程序。这种普通的模式很清晰,也让tests容易维护。对于切换了项目和开始基于一段新代码工作的人,这样的一致性非常有帮助。
  3. Tests应该具有可移植性可重用性。Google有很多平台独立的代码,它们的测试也应该是平台独立的。googletest可以在不同的操作系统运行,这些系统有不同的编译器(gcc,icc,和MSVC),有的支持异常有的不支持,所以googletest能够轻松的在各种配置下工作。
  4. 一旦tests失败,测试框架应该尽可能多的提供错误信息。googletest不会在第一个test失败后停下来,而是仅仅终止当前的test,然后继续下一个test。你也可以设置tests报告非致命错误,以此让当前test继续执行。因此,你能在一个run-edit-compile周期里,检测并修复多个bug。
  5. 测试框架应该把程序员从繁杂的琐事中解放出来,让他们专注于test内容。googletest自动跟踪所有已定义的tests,不需要用户为了运行而去枚举它们。
  6. Tests应该是快速的。使用googletest,你能够重用在tests中共享的资源,并且只需要调用一次set-up/taer-down,tests之间没有依赖关系。

googletest基于流行的xUint架构,所以如果你在此之前使用过JUnit或者PyUnit,你会感觉很熟悉。否则,你需要花10分钟学习这些基础,然后再开始应用。开始吧!

命名法须知

注意:由于对术语Test, Test CaseTest Suite的不同定义,可能会造成一些困惑,所以留意这些可能的误解。
以往,googletest开始用术语Test Case表示具有相关性的tests,然而,当前公开的一些框架是用Test Suite,包括International Software Testing Qualifications Board (ISTQB)和various textbooks on Software Quality。
在googletest中使用的术语Test,与ISTQB和其他框架使用的术语Test Case意义一样。
术语Test通常具有足够广泛的意义,也包括ISTQB定义的Test Case,所以这没有太多问题。但是在Google Test中使用的术语Test Case有一定的矛盾性,所以会让人迷惑。
googletest最近开始用术语Test Suite替换Test Case,更好的API是TestSuite。旧的TestCase API逐渐被弃用和重构。
所以,请留意这两个术语的不同定义:

意义 googletest术语 ISTQB术语
使用特定的输入值检验特定的程序执行路径并验证结果 TEST() Test Case

基本概念


使用googletest从写assertions开始,这是用于检查条件是否为真的表达式。一个assertion的结果可以是successnonfatal failure,或者fatal failure。如果一个fatal failure发生,当前的函数会被终止;否则程序会正常继续执行。
Tests用assertions去验证被测代码的行为。如果一个test挂了或者得到一个失败的assertion,这个test就是失败的;否则就是成功。
一个test case包含一个或多个tests。你应该将tests分组到不同的test cases中,以反映被测代码的结构。当一个test case中的多个tests要共享通用的对象和子程序时,你可以将它们放入test fixture类中。
一个test program能包含多个test cases。
我们现在解释如何写一个测试程序,从单个assertion级别开始,直到建立tests和test cases。

Assertions


googletest assertions是类似于函数调用的宏。通过对类或函数的行为设置assertions,来进行测试。当assertion失败,googletes打印assertion的源文件和它所在的行号,以及错误信息。你也可以提供定制的错误信息,这会添加在googletest信息后面。
测试同样的功能assertions有两个版本,在测试函数中它们会表现出不同的效果。当断言失败,==ASSERT_==版本会产生致命错误并终止当前函数。==EXPECT_==产生非致命错误,不会终止当前函数。==EXPEXCT_==总是被推荐的,因为它们允许多个错误在一个test里面被报告。然而,当断言处发生错误后继续执行程序没有意义,你就应该使用==ASSERT_==。
一个失败的==ASSERT_*==会立即从当前函数返回,所以可能会跳过在这之后的清理代码,这是否值得修复依赖于泄露的特性,所以记住这一点,如果你在断言错误后得到一个堆检测错误。
为了提供定制的失败信息,可以通过操作符==<<==简单的将信息输入宏,或者一系列这样的操作。一个例子:

1
2
3
4
5
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的==wchar_t==, ==TCHAR==in==UNICODE==模式,或者==std::wstring==)被输入断言宏,会在打印时转换程UTF-8。

基本的Assertions

这些assertions进行基本的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 |

记住,如果条件为false,==ASSERT_ ==产生一个致命错误并从当前函数返回,而==EXPECT_ ==产生一个非致命错误,函数会继续执行。无论哪种情况,一个断言失败意味着它包含测试失败。

可用性:Linux, Windows, Mac。

二元比较


这部分描述比较两个值的断言。
| 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 |

Value参数必须是能通过断言比较操作符比较的,否则会得到一个编译错误。我们过去要求参数支持能输入到==ostream==的==<<==操作符,但是现在不是必要的。如果支持==<<==,一旦断言失败,它会被调用来输出参数;否则googletest会尽可能用最合理的方式来打印它。更多细节以及如何定制化打印参数,参考gMock recipe。
断言能用户定义的类型也有效,但必须要定义相关的比较运算符(比如 == == ==, == < ==,等)。Google C++风格指南不鼓励运算符重载,所以你可能要使用== ASSERT_TRUE() == 或者 == EXPECT_TRUE() == 去断言自定义的两个对象是否相等。
然而,如果可能,==ASSERT_EQ(actual, expected)==要优于==ASSERT_TRUE(actual == expected)==,因为前者会在失败时报告==actual==和==expected==的值。
参数总是精确的计算一次。因此,参数产生的副作用是正常的。但是,就像任何正常的C/C++函数一样,参数的计算顺序是未定义的(即这个顺序是编译器相关的),并且你的代码不应该有任何特殊的参数计算顺序依赖。
==ASSERT_EQ==进行指针的相等判断。如果在两个C字符串上使用,这会测试它们是否有相同的内存位置,而不是是否有一样的值。因此,如果你要比较C字符串(比如==const char==)的值就用==ASSERT_STREQ()==,稍后会有介绍。特殊的,断言一个C字符串是==NULL==,使用==ASSERT_STREQ(c_string, NULL)==。如果支持C++11,考虑用ASSERT_EQ(c_string, nullptr)。用==ASSERT_EQ==比较两个==string==对象。
做指针比较时,用==
_EQ(ptr, nullptr)==和==NE(ptr, nullptr)==代替==_EQ(ptr, NULL)==和==_NE(ptr, NULL)==。因为==nullptr是类型而==NULL==不是。
如果你处理浮点数,你需要使用这些宏针对浮点的变化形式,以避免取整导致的问题。
这节的宏支持宽字符对象和窄字符对象(==string==和==wstring==)。
可用性:Linux, Windows, Mac。
历史改动:2016年二月以前,==
_EQ==有一个调用惯例==ASSERT_EQ(expected, actual)==,很多现有代码就使用这种顺序。现在==*_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 contents |
| 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 contents, ignoring case |

注意断言名字种的“CASE”意味着忽略了case,==NULL==指针和一个空的字符串是不同的。
== STREQ ==和== STRNE ==也接收宽的C字符串(== wchar_t* ==)。如果两个宽字符串比较失败,它们的值会被UTF-8的字符串替换打印。
可用性:Linux, Windows, Mac。
其他:更多的字符串比较技巧(例如,子串,前缀,后缀和正则匹配),参考高级googletest指南的这部分

简单的测试


建立一个测试:

  1. 用==TEST()==宏定义并命名一个测试函数,这就是通常的没有返回值的C++函数。
  2. 在这个函数种,可以包含你想要的各种有效的C++表达式,并用各种googletest断言去验证这些值。
  3. 测试的结果决定于这些断言;测试中的任何的断言失败(无论是致命的还是非致命的),或者测试挂了,整个测试都是失败的,否则,测试成功。
    1
    2
    3
    TEST(TestSuiteName, TestName) {
    ... test body ...
    }

==TEST()==参数从一般到具体。第一个参数是test case的名字,第二个参数是这个test case 中 test 的名字。两个名字都必须是有效的C++命名符,并且不能包含下划线(==_==)。一个测试的全名由它的test case和test名字组成。不同的test case中的test可以由相同的test名字。
例如,我们写一个简单的整型函数:

1
int Factorial(int n); // Returns the factorial of n

这个函数的test case可能会是这样:

1
2
3
4
5
6
7
8
9
10
11
12
// Tests factorial of 0.
Test(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}

googletest以test cases为单位对测试结果分组,所以逻辑相关的tests应该被放在一个test case里;换言之,==TEST()==的第一个参数应该一样。在上面的例子中,由两个tests,==HandlesZeroInput==和==HandlesPositiveInput==,它们都属于==FactorialTest==。
命名test cases 和 tests时,应该遵循与函数命名和类命名同样的惯例。
可用性:Linux, Windows, Mac。

测试装置:多个测试使用相同的数据配置


如果你发现你写的两个或多个tests操作相似的数据,你可以使用 test fixture。它能让你在不同的tests中重用相同的对象配置。
建立一个配置:

  1. 继承==::testing::Test==。从==protected:==开始写内容,也就是你想要从子类访问的配置成员。
  2. 在类中申明任意你想要使用的对象。
  3. 如果必要,写一个默认的构造函数或者==SetUp()==函数,用来给每个测试初始化对象。一个常见的错误是将==SetUp()==拼写成==Setup()==,小写了==u==,可以在C++11中使用==override==确保拼写正确。
  4. 如果必要,写一个析构函数或者==TearDown()==函数去释放在==SetUp()==中分配的资源。阅读这里的FAQ,学习什么时候用构造和析构,什么时候用==SetUp()/TearDown()==。
  5. 如果需要,给你的tests定义共享的子程序。

当使用fixture,用==TEST_F()==代替==TEST()==,前者能让你访问这个test fixture中的对象和子程序:

1
2
3
TEST_F(TestSuiteName, TestName) {
... test body ...
}

如同==TEST()==,第一个参数是test case的名字,但是对于==TEST_F(),必须是这个test fixture类的类名。你可能已经猜到:==_F==代表fixture。
遗憾的是,C++宏不允许我们创建一个宏来处理两种类型的测试。使用错误的宏会导致编译器错误。
你必须在==TEST_F()==使用test fixture之前定义它,否则会得到编译错误“virtual outside class declaration”。
对于每个用==TEST_F()==定义的test,googletest会在运行时创造一个新的test fixture,通过==SetUp()==立即初始化,运行test,通过==TearDown()==进行清理,然后删除test fixture。注意:同一个test case 中的不同tests拥有不同的test fixture对象,并且googletest总是在创造下一个test fixture之前删除上一个。googletest不会对多个tests用相同test fixture。任何一个test对test fixture的改变不会影响到其他的。

作为一个例子,我们写一个tests,用于测试命名==Queue==的FIFO队列,接口如下:

1
2
3
4
5
6
7
8
9
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==的被测类型,它的test fixture名应该是==FooTest==。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class QueueTest : public ::testing::Test {
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_;
};

本例中,不需要==TearDown()==,因为不必在每个test后进行清理,析构函数已经做了这些工作。
现在,用==TEST_F()==写tests以及fixture。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
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;
}

上面的程序用了==ASSERT_==和==EXPECT_==断言。使用的原则是:如果你想在断言失败后继续收到错误信息,就用==EXPECT_==;如果断言失败后继续执行已经没有意义,就用==ASSERT_==。例如,==Dequeue== test中的第二个断言是 =ASSERT_NE(nullptr, n)=, 我们之后应该废弃这个指针 ==n==,因为 ==n== 是 ==NULL== 会导致段错误。
当这些tests运行,以下的事情会发生:

  1. googletest构造一个 ==QueueTest== 对象(我们叫它 ==t1== )。
  2. ==t1.SetUp()== 初始化 ==t1==。
  3. 第一个test( ==IsEmptyInitially== )在 ==t1== 上运行。
  4. test结束后 ==t1.TearDown()== 清理垃圾。
  5. ==t1== 析构。
  6. 在另外一个 ==QueueTest== 对象上重复以上过程,这次运行 ==DequeueWorks== test。

可用性:Linux, Windows, Mac。

触发Tests


==TEST()==和==TEST_F()==通过googletest隐式的注册它们的tests。所以,与很多其他的c++测试框架不同,你不必为了运行tests而去再列明这些已定义的tests。
tests定义后,你可以通过 ==RUN_ALL_TESTS()== 运行它们,所有的tests都成功会返回 ==0==,否则是 ==1== 。注意 ==RUN_ALL_TESTS()== 运行所有在你的链接单元中的tests–它们可以来自不同的test cases,甚至不同的源文件。

一旦被触发, ==RUN_ALL_TESTS()== 宏:

  1. 保存所有googletest flags 的状态。
  2. 为第一个test创造一个test fixture。
  3. 通过 ==SetUp()== 初始化。
  4. 在fixture对象上运行test。
  5. 通过 ==TearDown()== 清理fixture。
  6. 删除fixture。
  7. 重新保存googletest flags的状态。
  8. 为下一个test重复以上过程,直到所有tests运行完。

如果致命失败发生,后续的步骤将被跳过。

重要:你不能忽略 ==RUN_ALL_TEST()== 的返回值,否则会得到一个编译错误。这样涉及的原则是:自动化测试服务决定一个test是否通过是基于它的退出码,而不是他的 stdout/stderr 输出;因此, ==main()== 函数必须返回 ==RUN_ALL_TESTS()== 的值。
同时,你只能调用 ==RUN_ALL_TESTS()== 一次。多此调用会引发一个高级的googletest特性冲突(例如,线程安全death tests),所以这是不支持的。

可用性:Linux, Windows, Mac。

写主函数


写你的main()函数,它应该返回 ==RUN_ALL_TESTS()== 的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#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 case 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

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

==::testing::InitGoogleTest()== 函数为googletest flags解析命令行参数,并且移除所有可识别的flags。这能让用户通过各种flags来控制test程序的行为,在AdvancedGuide中有详细介绍。调用 ==RUN_ALL_TESTS()== 之前必须调用它,否则flags不会被正确初始化。
在Windows上, ==InitGoogleTest()== 也支持宽字符串,所以它也能在 ==UNICODE==模式下编译。
也许你觉得编写所有这些主函数工作量太大了?我们完全同意,那也是为什么googletest提供了一个基本的main()实现。如果这符合你的需求,把你的test和gtest_main链接,你很适合这样去做。
注意:==ParseGUnitFlags()== 已被弃用,更推荐用 ==InitGoogleTest()==。

需要知道的限制


  • Google Test被设计成线程安全的。在 ==pthreads==库可用的系统上,实现是线程安全的。当前在其他系统(比如 Windows)上在两个线程中并发使用Google Test断言是线程不安全的。在大部分tests中这不是问题,因为assertions总是在主线程中执行。如果你需要帮助,你可以针对你的平台,在 ==gtest-port.h== 中自愿实现这些必要的同步原语。