简介:Google C++ Testing Framework为了什么?
Google C++Testing Framework帮助您编写更好的C++ tests。
无论您是在Linux、Windows还是Mac上工作,如果您编写的是C++ code,Google Test可以帮助您。
那么,什么是好的测试,以及Google C++ Testing Framework是如何符合这个标准的呢?我们相信:
1、Tests应该是*独立的、可重复的*。根据其他测试的结果的成功或失败来调试一个测试是很痛苦的。Google C++ Testing Framework对测试的隔离是通过在不同的对象上运行他们中的每一个来完成的。当测试失败时,Google C++ Testing Framework允许您单独运行它,以便进行快速调试。
2、Tests应该组织有序,并能反映被测代码的结构。Google C++ Testing Framework将相关的测试分组到可以共享数据和子例程的test cases中。这种常见的模式很容易识别,并使测试易于维护。当人们切换项目并开始使用新的代码库时,这种一致性尤其有用。
3、Tests应该是可移植和可重用的。开源社区有许多与平台无关的代码,它的测试也应该是平台无关的。Google C++ Testing Framework可以在不同的操作系统上工作,使用不同的编译器(gcc、MSVC或其他),不管有没有exceptions机制,所以Google C++ Testing Framework的测试可以通过各种不同的配置很容易的开始测试工作。(注意:当前版本只包含Linux的构建脚本——我们正在积极地为其他平台开发 脚本)。
4、当Tests失败时,他们应该提供尽可能多的关于这个问题的信息。在第一次测试失败时,Google C++ Testing Framework不会终止工作。它只会终止当前的测试,并继续下一个测试。您还可以对测试进行设 置,使之在当前测试继续执行后上报non_fatal failures。这样,就可 以在一个运行–编辑–编译周期中检测并修复多个bug。
5、Testing Framework应该将测试编写者从内部处理的杂事中解脱出来, 让他们专注于测试的内容。Google C++ Testing Framework自动跟踪所有已定义的测试,并且 不要求用户为了运行他们而对他们进行枚举。
6、Tests应该是快速的。使用Google C++ Testing Framework,您只需要启动和清除一次,就可以在整个测试中重用共享资源,而无需让测试相互依赖。
由于Google C++ Testing FrameWork基于流行的xUnit架构,如果您以前使用过JUnit或PyUnit,那么您就会感到很自在。如果没有,你需要大约10分钟的时间来学习基础知识并开始学习。So let’s go!
注意:我们有时将Google C++ Testing Framework非正式地称为Google Test。
命名问题
注意:由于术语测试(test)、测试用例(test case)和测试套件(test suite)的不同定义,可能会有一些混淆,所以要当心这些误区。
从历史上看,Google C++ Testing Framework首先开始使用术语测试用例(test case)来分组相关的测试(test),而当前的出版物,包括国际软件测试资格委员会(ISTQB)和各种软件质量的教科书都使用了测试套件(test suite)这个术语来命名它们。
在Google C++ Testing Framenwork中使用的相关术语测试(test),与ISTQB等的术语测试用例(test case)相对应。
术语测试(test)通常具有广泛的意义,包括ISTQB定义的测试用例(test case),所以这在这里不是什么大问题。但是,谷歌测试中使用的术语测试用例(test case)有点矛盾的意义,因而令人困惑。
不幸的是,在整个Google C++ Testing Framework中,若想不破坏独立的项目而使用测试套件(test suite)替换测试用例(test case)并不容易,这是因为TestCase是不同地方的公共API的一部分。
所以暂时,请注意这些术语的不同定义:
意义 GTest术语 ISTQB术语
使用特定的输入值来执行特定的程序路径并验证结果 TEST() Test Case
一些相关的测试组成的一个集合 Test Case Test Suite
建立一个新的测试项目
要使用Google Test编写一个测试程序,您需要将Google Test编译到一个库中,并用它来链接你的测试。我们为一些常见的系统提供构建文件(build file):msvc/:用于Visual Studio,xcode/:用于Mac Xcode,make/:用于GNU make,Codegear/:用于Borland C++Builder以及autotools脚本(已弃用)和CMakeLists.txt:用于CMake(推荐的),Google测试根目录下用的就是CMakeLists.txt。如果您的系统不在此列表中,您可以查看make/makefile以了解如何编译Google Test(一般来说就是,需要在头文件搜索路径中有GTEST_ROOT和GTEST_ROOT/include来编译src/gtest all.cc,其中GTEST_ROOT是Google Test的根目录)。
一旦您能够编译Google Test Library,您应该为您的测试程序创建一个项目或构建一个目标。确保在头文件搜索路径中有GTEST_ROOT/include,以便编译器在编译你的测试时可以找到“gtest/gtest.h”。设置您的测试项目,使之链接到Google Test Library(例如,在Visual Studio中,这是通过在gtest.vcproj中添加依赖关系来完成的)。
如果你还有问题,看看Google Test自己的测试是如何构建的,并以此为例。
基本概念
在使用Google Test时,首先要编写断言(assertions),这些断言是检查条件是否为真的声明。断言的结果可以是成功(success)、非致命的故障(nonfatal failure)或致命的故障(fatal failture)。如果发生致命的故障,它将中止当前的功能;否则,程序将继续正常工作。
测试使用断言来验证测试代码的行为。如果一个测试崩溃了或者有一个失败的断言,那么它就失败了;否则就是成功。
一个测试用例(test case)包含一个或多个测试(test)。您应该将您的测试分组到反映被测代码结构的test cases中。当测试用例(test case)中的多个测试(test)需要共享公共对象和子例程时,您可以将它们放入一个测试夹具(test fixtrue)类中。
一个测试程序(test program)可以包含多个测试用例(test case)。
现在,我们将解释如何编写一个测试程序(test program),从单个断言级别开始,并构建到测试(test)和测试用例(test case)。
断言
Google Test断言是类似函数调用的宏。您通过对其行为进行断言来测试一个类或函数。当断言失败时,Google Test将打印断言的源文件和行号位置,以及一条失败消息。您还可以提供一个定制的失败消息,它将被附加到Google Test的消息中。
断言是成对的,它们测试相同的东西,但对当前的函数有不同的影响。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 strings和string对象。如果一个宽字符串(wchar_t*,在Windows上的UNICODE模式的TCHAR*,或者std::wstring)被传输到一个断言中,那么当打印时它将被转换成UTF-8。
基本断言
这些断言执行基本的true/false条件测试。
Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition) conditon is false
记住,当它们失败时,ASSERT_*会产生一个致命的错误并从当前函数返回,而EXPECT_*产生一个非致命的错误,从而允许函数继续运行。在任何情况下,断言失败意味着它的内容测试失败。
可用性:Linux、Windows、Mac。
二进制(Binary)比较
这一段描述比较两个值的断言。
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
如果出现故障,Google Test将打印val1和val2。
Value参数必须与断言的比较运算符可比较,否则会出现编译错误。我们之前要求这个参数需要支持<<操作符,但是自从v1.6.0(如果<<被支持,在断言失败时它就会被调用打印参;;否则,Google Test将试图以最好的方式把它打印出来。要了解更多的细节,以及如何自定义打印参数,请参阅Google Mock recipe)后,它不再是必须的了。
这些断言也可以使用用户自定义的类型,但是只有在定义了相应的比较运算符(例如==、<等)时才可以使用。如果定义了相应的运算符,最好使用ASSERT_*()宏,因为它们不仅会输出比较的结果,还会输出两个操作数。
参数总是会被精确地计算一次。因此,参数是可以有点副作用的。然而,与任何普通的c/c++函数一样,参数的评估顺序是未定义的(例如,编译器可以自由选择任何顺序),并且您的代码不应该依赖于任何特定的参数评估顺序。
ASSERT_EQ()对于pointers类型,是比较pointer。如果用在两个C string上,它会测试它们是否位于相同的内存位置,而不是它们具有相同的值。因此,如果您想要比较C string(例如const char *)的值,请使用ASSERT_STREQ(),稍后将对此进行描述。特别是,要断言C string为NULL,则使用ASSERT_STREQ(NULL,c_string)。但是,要比较两个string对象,您应该使用ASSERT_EQ。
这一章节中的宏都适用于窄(narrow)的和宽(wide)的 string对象(string和wstring)。
可用性:Linux、Windows、Mac。
历史记录:2016年2月之前,*_EQ有一个叫做ASSERT_EQ(expected,actual)的惯例,所以很多现有的代码都使用这个顺序。现在*_EQ以同样的方式处理这两个参数。
String比较
这个组中的断言比较两个C strings。如果你想比较两个string对象,可以使用EXPECT_EQ,EXPECT_NE等等。
Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,str2); 这两个C string具有相同的内容
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); 这两个C string具有不同的内容
ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); 这两个C string有相同的内容,忽略大小写
ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); 这两个C string有不同的内容,忽略大小写
请注意,断言名称中的“CASE”意味着这种情况被忽略了。
STREQ和STRNE也接受宽的C strings(wchar_t*)。如果比较两个宽strings失败,它们的值将被打印成UTF-8格式的narrow strings。
NULL pointer和空string被认为是不同的。
可用性:Linux、Windows、Mac。
See also:对于更多的string比较技巧(例如,子字符串(substring)、前缀(prefix)、后缀(suffix)和正则表达式匹配(regular expression matching)),请参阅Advanced Google Test Guide.。
简单测试
创建test:
1、使用TEST()宏来定义和命名一个测试函数,这些是普通的没有返回值的 C++函数。
2、在这个函数中,你想要包含的任何有效的C++语句,都可以使用各种Google 测试断言来检查值。
3、测试的结果由断言决定;如果测试中的任何断言失败(致命的或非致命 的),或者是测试崩溃,说明测试失败。否则,就是成功。
TEST(testCaseName, testName) {
… test body …
}
TEST()参数从一般到具体。第一个参数测试用例(test case)的名称,第二个参数是测试用例(test case)中的测试(test)名称。两个名称都必须是有效的C++标识符,并且它们不应该包含下划线(_)。测试(test)的全名(full name)由包含的测试用例(test case)和它的个性名称组成。来自不同测试用例(test case)的测试(test)可以具有相同的个性名称。
举一个简单的整型函数的例子:
int Factorial(int n); // Returns the factorial of n
这个函数的一个测试用例(test case)可能是这样的:
// 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));
}
Google Test用测试用例(test case)来分组测试结果,所以逻辑相关的测试应该在同一个测试用例中;换句话说,他们的TEST()的第一个参数应该是相同的。在上面的例子中,我们有两个测试,HandlesZeroInput和HandlesPositveInput,它们属于同一个测试用例FactorialTest。
可用性: Linux, Windows, Mac。
测试夹具(Test Fixtures):在多个测试中使用相同的数据配置
如果您发现自己编写了两个或多个操作相同数据的测试,那么您可以使用一个测试夹具(test fixture)。它允许您在几个不同的测试中重用相同的对象配置。
创建夹具(fixture),只需:
1、从::testing::Test派生出一个类。以protected:或public:的方式创建主 体(body),因为我们想要从子类中访问fixture成员。
2、在类中,声明准备使用的所有对象。
3、如果有必要,编写一个默认的构造函数或SetUp()函数来为每个测试 准备对象。一个常见的错误是用一个小u来拼写SetUp()为Setup(), 不要让这种情况发生在你身上。
4、如果有必要,编写一个析构函数或TearDown()函数来释放您在SetUp() 中分配的所有资源。要了解什么时候应该使用构造/析构函数,何时 应该使用SetUp()/TearDown(),请阅读这个FAQ条目。
5、如果需要,为您的测试定义子程序来完成共享。
在使用fixture时,请使用TEST_F()而不是TEST(),因为它允许您访问测试夹具(test fixture)中的对象和子程序:
TEST_F(test_case_name, test_name) {
… test body …
}
与TEST()一样,第一个参数是测试用例(test case)名,但对于TEST_F()来说,这必须是测试夹具(test fixture)类的名称。你可能已经猜到了: _F是fixture。
不幸的是,C++宏系统不允许我们创建一个宏来处理这两种类型的测试。使用错误的宏会导致编译错误。
而且,在TEST_F()中使用测试夹具(test fixture)之前,您必须首先定义这个测试夹具(test fixture)类,否则您将会得到编译器错误“virtual outside class declaration”。
对于用TEST_F()定义的每个测试,Google Test将:
1、在运行时创建一个新的测试夹具(test fixture)
2、通过SetUp()立即初始化它
3、运行测试
4、调用TearDown()清理
5、删除测试夹具(test fixture)。注意,在同一个测试用例(test case)中不 同的测试(test)有不同的测试夹具(test fixture)对象,并且Google Test 总是在创建下一个测试夹具之前删除这个测试夹具。Google test不 会在多个测试中重用相同的测试夹具。一个测试夹具的任何更改都 不会影响其他测试。
作为一个例子,让我们为一个名为Queue的FIFO队列类编写测试,它有以下接口:
template // 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。
class QueueTest : public ::testing::Test {
protected:
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// virtual void TearDown() {}
Queue q0_;
Queue q1_;
Queue 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_*断言。经验规则是当您希望测试在断言失败后继续显示更多错误时使用EXPECT_*,在失败之后继续运行没有意义时使用ASSERT_*。例如,在Dequeue测试中第二个断言是ASSERT_TRUE(n != NULL),因为我们稍后需要对指针n解除引用,当n为NULL时,会导致segfault。
这些测试运行的过程:
1、Google Test构造了一个QueueTest对象(我们称它为t1)。
2、初始化t1 ,使用t1.SetUp()。
3、第一个测试(IsEmptyInitially)在t1上运行。
4、在测试结束后,t1.teardown()执行清理工作。
5、销毁t1。
6、上面的步骤在另一个QueueTest对象上重复,这次运行DequeueWorks 测试。
可用性:Linux、Windows、Mac。
注意:当一个测试对象被构建时,Google Test自动保存所有Google Test标志,并在对象被销毁时恢复它们。
调用Tests
TEST()和TEST_F()隐式地用Google Test来注册他们的测试。因此,与其他许多C++测试框架不同,您不需要重新列出所有已定义的测试就可以运行他们。
在定义了测试之后,您可以使用RUN_ALL_TESTS()运行它们,如果所有的测试都成功了,则返回0,否则就会返回1。请注意RUN_ALL_TESTS()会在您的链接单元中运行所有的测试——它们可以来自不同的测试用例(test case),甚至是不同的源文件。
调用RUN_ALL_TESTS()宏时:
1、保存所有Google Test标志的状态。
2、为第一个测试创建一个测试夹具(test fixture)对象。
3、通过SetUp()初始化对象。
4、在fixture对象上运行测试。
5、通过TearDown()清除fixture。
6、删除fixture。
7、恢复所有Google Test标志的状态。
8、重复上述步骤进行下一次测试,直到所有测试都运行为止。
此外,如果测试fixture的构造函数在步骤2中生成致命的故障,那么步骤3-5没有任何意义,因此跳过了它们。类似地,如果第3步产生致命的失败,步骤4将被跳过。
重要:您不能忽略RUN_ALL_TESTS()的返回值,否则gcc会给您一个编译错误。这种设计的基本原理是,自动化测试服务根据它的退出代码来确定测试是否已经通过,而不是基于它的stdout/stderr输出;因此main()函数必须返回RUN_ALL_TESTS()的值。
另外,您应该只调用RUN_ALL_TESTS()一次。多次调用它与一些高级的Google Test特性(例如线程安全的死亡测试)相冲突,因此不支持。
可用性:Linux、Windows、Mac。
编写main()函数
你可以从这个样板开始:
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.
}
} // namespace
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
::testing::InitGoogleTest()函数解析命令行的Google Test标志,并删除所有它可以处理的标志。这使得用户可以通过各种标志来控制测试程序的行为,我们将在高级指南中介绍它。您必须在调用RUN_ALL_TESTS()之前调用此函数,否则标志将无法正确初始化。
在Windows上,InitGoogleTest()也适用于宽字符串,因此它也可以用于在UNICODE模式中编译的程序。
但是,也许您认为编写所有这些main()函数太复杂了?我们完全同意你的观点,这就是为什么Google Test提供了main()的基本实现。如果它符合您的需求,那么只需将您的测试与gtest_main库连接起来,您就可以开始了。
从这里开始
恭喜你!您已经学习了Google Test基础知识。您可以开始编写和运行Google Test测试,阅读一些示例(samples),或者继续使用高级指南(AdvanceGuide),该指南描述了许多更有用的Google测试特性。
已知的限制
Google Test被设计为线程安全的。在pthreads库可用的系统上,实现是线程安全的。目前,在其他系统(如Windows)上同时使用两个线程的Google Test断言是不安全的。在大多数测试中,这不是一个问题,通常断言是在主线程中完成的。如果您想要帮助,您可以自愿在你的平台中为gtest-port.h中实现必要的同步原语。