一、设置一个新的测试项目
在用 google test 写测试项目之前,需要先编译 gtest 到 library 库并将测试与其链接。我们为一些流行的构建系统提供了构建文件: msvc/ for Visual Studio, xcode/ for Mac Xcode, make/ for GNU make, codegear/ for Borland C++Builder.
如果你的构建系统不在这个名单上,在 googletest 根目录有 autotools 的脚本(不推荐使用)和 CMakeLists.txtCMake(推荐)。你可以看看 make / Makefile 来了解如何编译 Google Test(基本上你想在头文件中使用 GTEST_ROOT 和 GTEST_ROOT / include 来编译 src / gtest-all.cc 路径,其中 GTEST_ROOT 是 Google Test根目录)。
一旦你能够编译 google test 库,您应该为您的测试程序创建一个项目或构建目标。Make sure you have GTEST_ROOT/include in the header search path so that the compiler can find “gtest/gtest.h” when compiling your test. 把 google test 库加到你的测试项目中(比如:在 VS 中在 gtest.vcproj 上添加依赖)。
二、基本概念
当使用谷歌测试,您首先要写断言,断言是检查条件是否为真的语句。一个断言的结果可以是成功,非致命性失败,或致命的失败。如果一个致命失败出现,它会终止当前的函数;否则程序继续正常运行。
测试使用断言验证代码的行为。如果一个测试崩溃或者有一个失败的断言,那么失败;否则成功。
一个测试用例包含一个或多个测试。 您应该将测试分组为反映测试代码结构的测试用例。当测试用例中的多个测试需要共享公共的对象和子程序时,你可以把它们放进一个test fixture class(测试夹具类)。
一个测试程序可以包含多个测试用例。
现在我们将解释如何编写测试程序,从单个断言级别开始,并构建测试和测试用例。
三、断言
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;
}
示例:
EXPECT_EQ(0, strcmp(s.c_string(), kHelloString2)) << "s.c_string:" << s.c_string() << " kHelloString:" << +kHelloString ;
任何可以流式传输到 ostream 的东西都可以流式传输到断言宏,特别是 C 字符串和字符串对象。 如果一个宽字符串(Windows 上的 wchar_t ,UNICODE 模式的TCHAR ,或者 std :: wstring)被流传输到一个断言,当打印时它将被转换为 UTF-8。
四、基本断言
这些断言做基本的真 / 假条件测试。
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false
记住,当它们失败时,ASSERT*产生致命失败并从当前函数返回,而 EXPECT*产生非致命失败,允许函数继续运行。 在任一情况下,断言失败意味着其包含的测试失败。
五、二进制比较
本节介绍比较两个值的断言。
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。
值参数通过断言的比较运算符必须可以比较,否则会出现编译错误。我们曾经要求参数支持 << 运算符,用于流传输到 ostream,但从 v1.6.0 它不再需要(如果支持 <<,则会在断言失败时调用它来打印参数; 否则 Google Test 将尝试以最佳方式打印它们。有关更多详细信息和如何自定义参数的打印,请参阅此 Google Mock recipe.。
这些断言可以使用用户定义的类型,但是只有当你定义了相应的比较运算符(例如 ==,<,etc)。如果定义了相应的操作符,则更喜欢使用 ASSERT_*()宏,因为它们不仅会打印比较结果,而且还会打印出两个操作数。
参数总是只计算一次。因此,参数有副作用没关系。然而,与任何普通的 C /C++函数一样,参数的求值顺序是未定义的(即编译器可以自由选择任何顺序),你的代码不应该依赖于任何特定的参数求值顺序。
ASSERT_EQ()指针的指针相等。如果在两个 C 字符串上使用,它会测试它们是否在同一个内存位置,而不是它们具有相同的值。因此,如果你想比较 C 字符串(例如 const char *)的值,使用 ASSERT_STREQ(),稍后将会描述。特别地,要断言 C 字符串为 NULL,请使用 ASSERT_STREQ(NULL,c_string)。但是,要比较两个字符串对象,应该使用 ASSERT_EQ。
本节中的宏适用于窄和宽字符串对象(string 和 wstring)。
历史记录:2016 年 2 月之前 _EQ 有一个约定,称为 ASSERT_EQ(expected,actual),所以很多现有的代码使用这个顺序。 现在 _EQ 以相同的方式处理这两个参数。
六、字符串比较
该组中的断言比较两个 C 字符串的值。 如果要比较两个字符串对象,请改用 EXPECT_EQ,EXPECT_NE 和 etc。
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,_str_2); the two C strings have the same content
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); the two C strings have different content
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 content, ignoring case
注意,断言名中的 “CASE” 表示忽略大小写。
STREQ和STRNE也接受宽 C 字符串(wchar_t *)。 如果两个宽字符串的比较失败,它们的值将打印为 UTF-8 窄字符串。
NULL 指针和空字符串被认为是不同的。
可用OS:Linux,Windows,Mac。
另请参阅:有关更多字符串比较技巧(例如,子字符串,前缀,后缀和正则表达式匹配),请参见高级 Google Test指南 (Advanced Google Test Guide.)。
七、简单测试
创建测试:
使用 TEST() 宏来定义和命名测试函数,这些是不返回值的普通 C++函数。
在此函数中,连同要包括的任何有效的 C++语句,使用各种 Google Test 断言来检查值。
测试的结果由断言确定; 如果测试中的任何断言失败(致命或非致命),或者如果测试崩溃,则整个测试失败。 否则,它成功。
TEST(test_case_name, test_name) {
... test body ...
}
TEST()参数从一般到特定。 第一个参数是测试用例的名称,第二个参数是测试用例中的测试名称。 这两个名称必须是有效的C++标识符,并且它们不应包含下划线(_)。 测试的全名由其包含的测试用例及其个人名称组成。来自不同测试用例的测试可以具有相同的个人名称。
例如,让我们使用一个简单的整数函数:
int Factorial(int n); // Returns the factorial of n;n!
此函数的测试用例可能如下所示:
// 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()的第一个参数应该是相同的。 在上面的例子中,我们有两个测试,HandlesZeroInput 和 HandlesPositiveInput,属于同一个测试用例 FactorialTest。
八、测试夹具 (Test Fixtures):对多个测试使用相同的数据配置
如果你发现自己写了两个或更多的测试来操作类似的数据,你可以使用测试夹具。它允许您为几个不同的测试重复使用相同的对象配置。
要创建夹具,只需:
从:: testing :: Test 派生一个类。 使用 protected:或 public:开始它的主体,因为我们想从子类访问 fixture 成员。
在类中,声明你打算使用的任何对象。
如果需要,可以编写默认构造函数或 SetUp()函数来为每个测试准备对象。 一个常见的错误是拼写 SetUp()为 Setup(),不要让这种情况发生在你身上。
如果需要,写一个析构函数或TearDown()函数来释放你在 SetUp()中分配的任何资源。 要学习什么时候应该使用构造函数 / 析构函数,当你应该使用 SetUp()/ TearDown()时,请阅读这个 FAQ entry。
如果需要,定义要分享的测试的子程序。
当使用夹具时,使用 TEST_F()而不是 TEST(),因为它允许您访问测试夹具中的对象和子程序:
TEST_F(test_case_name, test_name) {
... test body ...
}
和 TEST()一样,第一个参数是测试用例名,
但是对于 TEST_F()第一个参数必须是测试夹具类的名称。 你可能猜到了:_F 是夹具。
不幸的是,C++ 宏系统不允许我们创建一个可以处理两种类型的测试的宏。 使用错误的宏会导致编译器错误。
另外,在 TEST_F()中使用它之前,你必须首先定义一个测试夹具类,否则将得到编译器错误 “virtual outside class declaration”。
对于使用 TEST_F()定义的每个测试,Google Test 将:
在运行时创建一个新的测试夹具
立即通过 SetUp()初始化,
运行测试
通过调用 TearDown()清除
删除测试夹具。 请注意,同一测试用例中的不同测试具有不同的测试夹具对象,Google Test始终会删除测试夹具,然后再创建下一个测试夹具。 Google Test不会为多个测试重复使用相同的测试夹具。一个测试对夹具的任何更改不会影响其他测试。
例如,让我们为名为 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:
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// virtual void TearDown() {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
在这种情况下,不需要 TearDown(),因为我们不必在每次测试后清理,除了析构函数已经做了什么。
现在我们将使用 TEST_F()和这个夹具编写测试。
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断言。 经验法则 (The rule of thumb) 是当你希望测试在断言失败后继续显示更多错误时使用 EXPECT_,或是在失败后继续使用 ASSERT_*没有意义。 例如,Dequeue 测试中的第二个断言是 ASSERT_TRUE(n!= NULL),因为我们需要稍后解引用指针 n,这将导致 n 为 NULL 时的 segfault。
当这些测试运行时,会发生以下情况:
1.Google Test 构造了一个 QueueTest 对象(我们称之为 t1)。
2.t1.SetUp()初始化 t1。
- 第一个测试(IsEmptyInitially)在 t1 上运行。
4.t1.TearDown()在测试完成后清理。
5.t1 被析构。
- 以上步骤在另一个 QueueTest 对象上重复,这次运行 DequeueWorks 测试。
九、调用测试
TEST()和 TEST_F()用 Google Test 隐式注册他们的测试。 因此,与许多其他C++测试框架不同,您不必重新列出所有定义的测试以运行它们。
定义测试后,可以使用 RUN_ALL_TESTS()运行它们,如果所有测试成功则返回 0,否则返回 1。 请注意,RUN_ALL_TESTS()运行链接单元中的所有测试 - 它们可以来自不同的测试用例,甚至是不同的源文件。
调用时,RUN_ALL_TESTS()宏:
保存所有 Google Test标记的状态。
为第一个测试创建测试夹具对象。
通过 SetUp()初始化它。
在 fixture 对象上运行测试。
通过 TearDown()清除夹具。
删除夹具。
恢复所有 Google Test标志的状态。
重复上述步骤进行下一个测试,直到所有测试运行结束。
此外,如果测试夹具的构造函数在步骤 2 中产生致命故障,则步骤 3-5 没有意义,因此它们被跳过。 类似地,如果步骤 3 产生致命故障,则将跳过步骤 4。
重要:您不能忽略 RUN_ALL_TESTS()的返回值,否则 gcc 将给您一个编译器错误。 此设计的基本原理是自动测试服务基于其退出代码而不是其 stdout / stderr 输出来确定测试是否已通过; 因此您的main()函数必须返回 RUN_ALL_TESTS()的值。
此外,您应该只调用一次 RUN_ALL_TESTS()。 多次调用它会与一些高级 Google Test功能(例如线程安全死亡测试)冲突,因此不受支持。
十、写 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标志的命令行,并删除所有已识别的标志。 这允许用户通过各种标志控制测试程序的行为,我们将在 AdvancedGuide 中介绍。 在调用 RUN_ALL_TESTS()之前必须调用此函数,否则标志将无法正确初始化。
在 Windows 上,InitGoogleTEST()也适用于宽字符串,因此它也可以在以 UNICODE 模式编译的程序中使用。
但也许你认为编写所有这些 main()函数是太多的工作? 我们完全同意你的看法,这就是为什么 Google Test 提供了 main()的基本实现。 如果它适合你的需要,然后只是链接你的测试与 gtest_main 库。
十 一、VisualC++用户的重要说明
如果你把你的测试放入一个库,你的 main()函数在不同的库或在你的 .exe 文件中,这些测试将不会运行。 原因是 VisualC++中的一个错误。 当您定义测试时,Google Test会创建一些静态对象来注册它们。 这些对象没有从其他地方引用,但它们的构造函数仍然应该运行。 当 VisualC++链接器发现库中没有从其他地方引用时,它会抛出该库。 您必须通过主程序中的测试来引用您的库,以防止链接器丢弃它。 这里是如何做到的。 在你的库代码中声明一个函数:
。。。。。省略啦,如果用 VC 再来看。
十二、从这里到哪里去
恭喜! 您已学习了 Google Test基础知识。 您可以开始编写和运行 Google Test 测试,阅读一些示例,或继续阅读 AdvancedGuide,其中介绍了更多有用的 Google Test功能。
十三、已知的限制
Google Test旨在线程安全。 在 pthreads 库可用的系统上,它的实现是线程安全的。 目前,在其他系统(例如 Windows)上同时使用两个线程的 Google Test 断言是不安全的。 在大多数测试中,这不是一个问题,因为通常断言是在主线程中完成的。 如果你想提供帮助,你可以志愿基于您的平台在 gtest-port.h 中实现必要的同步原语。