原文地址
简介:为什么使用googletest
googletest帮助你写更好的C++测试。 googletest是由测试技术团队根据Google的特定要求和约束开发的测试框架。无论您是在Linux,Windows还是Mac上工作,如果您编写C ++代码,googletest都可以为您提供帮助。 它支持任何类型的测试,而不仅仅是单元测试。 那么,什么是一个好的测试,以及googletest是怎么发挥作用的?我们相信:
- 测试应该是独立的和可以重复的。调试因其他测试而成功或失败的测试是非常痛苦的,googletest通过在不同的对象上运行每个测试来隔离测试。 如果测试失败,则googletest允许您单独运行它以进行快速调试
- 测试应该井井有条,并能反映出测试代码的结构。 googletest将相关测试分组到可以共享数据和子例程的测试套件中。 这种常见的模式易于识别,并使测试易于维护。 当人们切换项目并开始在新的代码库上工作时,这种一致性特别有用。
- 测试应该是可移植且可重复使用的。 Google有很多与平台无关的代码; 其测试也应该与平台无关。 googletest可在不同的操作系统,不同的编译器上运行,无论有无例外,因此googletest测试可以在多种配置下运行。
- 当测试失败时,它们应该尽可能的提供关于故障的信息。googlet不会在第一次测试失败时停止,相反,它只会停止当前测试并继续下一个。您还可以设置报告非致命故障的测试,然后继续当前测试。因此,您可以在一个运行-编辑-编译循环中检测并修复多个错误。
- 测试框架应该讲测试编写者从杂活中解放出来,并让它们聚焦于测试内容。googletest自动跟踪所有定义的测试,并且不需要用户列举它们来运行它们
- 测试应该很快。 借助googletest,您可以在测试之间重用共享资源,并且只需支付一次设置/拆卸费用,而无需使测试相互依赖 由于googletest是基于流行的xUint架构,如果你以前使用过Juint或者PyUint,你将感觉到宾至如归。如果没有,你需要大约10分钟来学习基础知识并开始使用。所以让我们开始吧!
注意术语
- 注意:术语“测试”,“测试用例”和“测试套件”的不同定义可能会引起混淆,因此请注意不要误解这些术语
历史上,gongletest开始使用Test Case来对相关的测试进行分组,而当前的出版物,包括国际软件测试资格委员会(internationalsoftware Testing Qualifications Board,ISTQB)的材料和各种软件质量教科书,都使用术语Test Suite来进行分组。
在googletest中使用的相关术语Test对应于ISTQB和其他术语testcase。
Test通常具有足够广泛的意义,包括ISTQB对est Case的定义,因此在这里并没有太大的问题。 但是Google Test中使用的est Case一词具有矛盾的含义,因此令人困惑.
googletest最近开始用Test Suite替换术语Test Case。首选的API是TestSuite。旧的Test Case Api正在慢慢地被弃用和重构。
所以请注意这些术语的不同定义:
| Meaning | googletest Term | ISTQB Term |
|---|---|---|
| 使用特定的输入值执行特定的程序路径并验证结果 | TEST() | Test Case |
基本概念
在使用googletest时,首先要编写断言,即检查条件是否为真的语句。断言的结果可以是成功、非致命失败或致命失败。如果发生致命故障,它将中止当前功能;否则程序将正常继续。
Tests使用断言来验证测试代码的行为,如果一个测试崩溃了或者断言失败,那么它将失败,否则它是成功的
一个test suite包含一个或多个测试,你应该将你的测试分组到可以反映测试代码结构的test suites中,当单个test suite中的多个测试需要共享共同的对象或者子程序时 你可以把他们放到一个test fixture类。
一个测试程序可以包含多个test suites.
我们现在将解释如何编写测试程序,从单个断言级别开始,逐步构建测试和测试套件
断言
googletest断言是类似于函数调用的宏,你可以断言其行为来测试类和函数,当断言失败,googletest会打印一条带有断言源文件和行号位置的失败信息,您还可以提供自定义失败消息,该消息将附加到googletest的消息中。
断言成对出现,测试相同事务,但是对当前函数的影响并不相同。ASSERT_*版本失败时会产生致命失败并中止当前函数。EXPECT_*产生非致命失败并不会中止当前函数。通常EXPECT_*更好,因为它们允许在一个测试中可以报告多个失败。但是,如果在所声明的断言失败后继续执行没有意义,则应使用ASSERT_ *。
由于失败的ASSERT_ *立即从当前函数返回,可能会跳过其后的清除代码,因此可能导致空间泄漏。根据泄漏的性质,它可能值得修复,也可能不值得修复——因此,如果除了断言错误之外还出现堆检查器错误,请记住这一点。
要提供自定义失败消息,只需使用<<操作符或此类操作符序列将其流式传输到宏中即可。 一个例子:
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字符串和字符串对象。 如果将宽字符串(wchar_t *,Windows上的UNICODE模式下的TCHAR *或std :: wstring)流式传输到声明,则在打印时将其转换为UTF-8。
基本断言
这些断言进行基本的真/假条件测试
| Fatal assertion | Nonfatal assertion | Verifies |
|---|---|---|
| ASSERT_TRUE(condition) | EXPECT_TRUE(condition) | condition is true |
| ASSERT_FALSE(condition) | EXPECT_FALSE(condition) | condition is 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 |
传给断言比较操作的值参数必须是可比较的,否则你会得到一个编译错误。我们以前需要参数来支持<<操作符,以便流式传输到ostream,但现在不再需要了.如果支持<<,在断言失败时,它将被调用来打印参数,否则googletest将试图以最好的方式打印它们。
这些断言可以用于用户定义的类型,但前提是您定义了相应的比较运算符(例如==或<)。由于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()。特殊的,使用ASSERT_STREQ(c_string, NULL)断言C字符串是否为NULL。如果支持C++11,考虑使用ASSERT_EQ(c_string, nullptr)。为了比较两个string对象,应该使用ASSERT_EQ。
当进行指针对比时使用*_EQ(ptr, nullptr)和*_NE(ptr, nullptr)替代*_EQ(ptr, NULL)和*_NE(ptr, NULL)。这是因为nullptr是类型化的,而NULL不是。
如果您使用的是浮点数,则可能需要使用这些宏中某些宏的浮点数变体,以避免舍入引起的问题。有关详细信息,请参阅高级googletest主题。
本节中的宏可用于窄字符串和宽字符串对象(string和wstring)。
可用性:Linux,Windows,Mac
历史记录:2016年2月之前,*_EQ约定将其称为ASSERT_EQ(expected, actual),因此许多现有代码都使用此顺序。 现在*_EQ以相同的方式对待两个参数。
字符串比较
该组中的断言比较两个C字符串。 如果要比较两个字符串对象,请改用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 |
*STREQ*和*STRNE*也接受宽C字符串(wchar_t *)。如果两个宽字符串的比较失败,则它们的值将打印为UTF-8窄字符串。 | ||
| 可用性:Linux、Windows、Mac。 |
简单测试
创建测试:
- 使用
Test()宏定义和命令测试函数,这些是没有返回值的普通C++函数 - 在此函数中,要与要包含的所有有效
C++语句一起使用各种googletest断言来检查值。 测试的结果由断言决定;如果测试中的任何断言失败(致命或非致命),或者如果测试崩溃,则整个测试失败。否则,它就会成功。
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(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()的第一个参数应该相同。 在上面的示例中,我们有两个测试,HandlesZeroInput和HandlesPositiveInput,它们属于同一个测试套件FactorialTest。
命名测试套件和测试时,应遵循与命名函数和类相同的约定。
可用性:Linux、Windows、Mac。
测试夹具:对多个测试使用相同的数据配置{#same-data-multiple-tests}
如果您发现自己编写了两个或多个对类似数据进行操作的测试,那么可以使用测试夹具。这允许您为几个不同的测试重用相同的对象配置。 创建夹具:
- 从
::testing::Test派生类,从protected:开始它的主体,因为我们要从子类访问夹具成员。 - 在类的内部声明,声明你计划使用的任何对象
- 如有必要,编写默认的构造函数或
SetUp()函数为每个测试准备对象。 一个常见的错误是使用小写的u将SetUp()拼写为Setup()在C++11中使用override确保其拼写正确。 - 如有必要,编写一个析构函数或
TearDown()函数以释放您在SetUp()中分配的所有资源。 若要了解何时应使用构造函数/析构函数以及何时应使用SetUp()/TearDown(),请阅读FAQ。 - 如果需要,定义要共享的测试子例程。
使用夹具时,请使用
TEST_F()替代TEST(),因为它允许你访问测试夹具中的对象和子例程:
TEST_F(TestFixtureName, TestName) {
... test body ...
}
像TEST()一样,第一个参数是测试套件名称,但是对于TEST_F(),它必须是测试夹具类的名称。 您可能已经猜到了_F用于固定夹具。
不幸的是,C ++宏系统不允许我们创建可以处理两种类型的测试的单个宏。 使用错误的宏会导致编译器错误。
另外,您必须先定义一个测试夹具类,然后才能在TEST_F()中使用它,否则您将得到编译器错误“虚拟外部类声明”。
对于使用TEST_F()定义的每个测试,googletest将在运行时创建一个新的测试夹具,立即通过SetUp()对其进行初始化,运行该测试,通过调用TearDown()进行清理,然后删除该测试夹具。请注意,同一测试套件中的不同测试具有不同的测试夹具对象,并且googletest总是在创建下一个测试夹具之前将其删除。googletest不会将同一测试夹具重复用于多个测试。一个测试对固定夹具所做的任何更改均不会影响其他测试。
例如,让我们为名为Queue的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;
...
};
首先,定义一个夹具类。 按照惯例,您应该给它命名为FooTest,其中Foo是要测试的类。
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_F()和此夹具编写测试。
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_*。 例如,出队测试中的第二个断言是ASSERT_NE(nullptr,n),因为我们稍后需要取消对指针n的引用,这会在n为NULL时导致段错误。
这些测试运行时,会发生以下情况:
- googletest构造一个
QueueTest对象(t1) ti.SetUp()初始化t1- 第一个测试(IsEmptyInitially)在
t1上运行 - 测试完成之后,
ti.TearDown()清理资源 t1析构- 在另一个QueueTest对象上重复上述步骤,这次运行DequeueWorks测试。 可用性:Linux、Windows、Mac。
调用测试
TEST()和TEST_F()向googletest隐式注册其测试。与许多其他C++测试框架不同,你不必重新列出所有定义的测试即可运行它们。
定义测试后,可以使用RUN_ALL_TESTS()运行它们,如果所有测试成功,则返回0,否则返回1。请注意,RUN_ALL_TESTS()在链接单元中运行所有测试-它们可以来自不同的测试套件,甚至来自不同的源文件。
调用时,RUN_ALL_TESTS()宏:
- 保存所有googletest标志的状态。
- 为第一个测试创建一个测试夹具对象。
- 通过
SetUp()对其进行初始化。 - 在夹具对象上运行测试。
- 通过
TearDown()清理夹具。 - 删除夹具。
- 恢复所有googletest标志的状态。
- 对下一个测试重复上述步骤,直到所有测试都已运行。 如果发生致命故障,将跳过后续步骤:
重要说明:你一定不能忽略RUN_ALL_TESTS()的返回值,否则会出现编译器错误。 这种设计的基本原理是,自动化测试服务将根据其退出代码(而不是根据其stdout/stderr输出)来确定测试是否通过。 因此您的main()函数必须返回RUN_ALL_TESTS()的值。
另外,您应该只调用一次RUN_ALL_TESTS()用它会与某些高级googletest功能(例如线程安全的死亡测试)发生冲突,因此不被支持。
可用性:Linux、Windows、Mac。
编写main()函数
大多数用户不需要编写自己的main函数,而是与gtest_main(与gtest相对)链接,后者定义了合适的入口点。 有关详细信息,请参见本节末尾。本节的其余部分仅适用于需要在测试运行前做一些自定义的事情,而这些事情不能在固定夹具件的框架内表达。
如果您编写自己的ain数,它应该返回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 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).
}
// 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();
}
::esting::InitGoogleTest()令行中的googletest标志,并删除所有可识别的标志。 这允许用户通过各种标志控制测试程序的行为,我们将在dvancedGuide介绍这些标志。 您必须在调用UN_ALL_TESTS()函数,否则标志将无法正确初始化。
在Windows上,itGoogleTest()宽字符串,因此它也可以用于以NICODE式编译的程序。
但是也许你认为功能工作量太大? 我们完全同意你因此Google Test提供了ain()。 如果适合您的需求,则只需将测试与test_main链接就可以了。
注意:不赞成使用arseGUnitFlags()推荐使用nitGoogleTest()
已知局限性
- Google Test被设计成线程安全的,在具有pthreads库的系统上,该实现是线程安全的,目前在其他系统(例如Windows)上同时使用两个线程的Google Test断言是不安全的。在大多数测试中,这不是问题,通常断言是在主线程中完成的。如果您想提供帮助,可以自愿在gtest port.h中为您的平台实现必要的同步原语。