Google Test V1_5_Primer 翻译

  •   引言:为什么选择Goolge C++测试框架
  •   构建一个新的测试工程
  •   基本概念
  •   断言 
  •         基本断言
  •         二元比较
  •         字符串计较   
  •   简单测试
  •   测试夹具(Test Fixtures):对多个测试使用相同的数据配置(data configuration)
  •   调用测试
  •   编写main()函数
  •         Visual C++用户注意事项
  •   从这里出发(Where to go from Here)
  •   缺陷和不足

引言:为什么选择Google C++测试框架?

Goolge C++ 测试框架能帮助你编写更好的C++测试。

无论你是在Linux, Windows还是Mac上进行开发工作,只要你编写C++代码,Goolge Test都能够给你帮助。

那么一个好的测试有什么要求,Goolge C++测试框架将如何满足这样的要求?我们相信:

    1. 测试必须是独立的(independent)和可重复的(repeatable)。如果一个测试成功或失败取决于其他测试,调试这样的一个测试时会是非常痛苦的。Goolge C++测试框架通过在不同的对象上运行每一个测试来对各个测试进行隔离。一旦一个测试失败了,Goolge C++测试框架允许你在隔离的情况下进行快速调试。
    2. 测试必须被合理组织并且能够反映被测代码的结构。Goolge C++ 测试框架将有关联的测试分组成不同的测试案例,这些测试案例之间可以共享数据和子程序。
    3. 测试必须是可移植的(portable)和可复用的(reusable)。开源社区有很多平台中立的的代码,这些代码的测试也应当是平台中立的。Goolge C++测试框架可以搭配不同的编译器 (gcc, MSVC,和其它编译器),在不同的操作系统上使用(支持或不支持异常处理)。因此使用Goolge C++测试框架编写的测试能够和各种配置一起配合使用。(注意:当前的发布版本仅包含针对Linux的编译脚本—我们正在努力编写针对不同平台的脚本。)
    4. 当测试失败时,应当尽可能多地提供和问题相关的信息。当第一个测试失败时,Goole C++测试框架并不立即停止。相反,测试框架仅仅停止当前的测试,而后继续执行后续的测试。你也可以构建这样的测试:在报告非致命错误后当前测试继续进行。这样,你可以在一次“运行-编辑-编译”循环中检测和修复多个错误。
    5. 测试框架应当将测试人员从繁杂的日常琐事中解放出来,让他们关注于测试内容(content)。Goolge C++ 测试框架自动对所有已定义的测试进行跟踪,并且不要求用户在运行测试前对各个测试进行枚举便。
    6. 测试必须是快速的(fast)。使用Goolge C++测试框架,你仅需要一次创建/拆除操作,便可以在不同测试间复用共享资源,而不需要构建相互依赖的测试。

由于Goole C++测试框架基于流行的xUnit架构,如果曾经使用过JUnit或PyUnit,你将会感觉得心应手。即便没有这样的经验,也只需要大约10分钟,便可以学会基本的原理并且使用它。那么,让我们开始吧!

:我们有时把Goole C++测试框架简称为Goolge Test。

构建一个新的测试工程

要使用Goole Test编写一个测试程序,首先要将Goolge Test编译成一个库文件并链接到你的测试中。我们为一些流行的编译系统(如对于Visual Studio有msvc/,为Mac Xcode提供了xcode/,为GNU make提供了make/,为Borland C++ Builder提供了codegear/,为Scons提供了scons/,并且在Goolge Test的根目录下提供了自动工具脚本(autotolls script))。如果你的编译系统不在这个列表内,你可以参考make/Makefile来学习如何编译Goolge Test(通常你需要编译将 GTEST_ROOT 和 GTEST_ROOT/include 包含在你的头文件索引路径下,然后编译 src/gtest-all.cc。GTEST_ROOT 是Goolge Test的根目录)。

一旦成功编译了Goolge Test库,你需要为测试程序建立一个工程或编译对象(build target)。请确保将 GTEST/include 包含在头文件索引路径中,使编译器在编译测试程序能够找到 头文件。建立测试工成并链接 Goole Test 库(例如,在Visual Studio中是通过在gtest.vcproj中增加一个关联来实现的)。

如果还有其他的问题,请参考Goolge Test自带的测试示例,参考它们是如何被编译的。

基本概念

使用Goolge Test,最开始接触的就是编写断言(assertions)。断言就是检查条件是否为真的语句。断言的结果可能使成功(success),非致命错误(nonfatal failure)或者致命错误(fatal failure)。如果出现了一个致命错误,就跳出当前函数;否则程序继续正常运行。

测试使用断言来校验被测代码的行为。如果一个测试崩溃或者出现一个失败断言,那么这个测试就认为是失败的;否则这个测试就是成功的。

一个测试案例包含一个或多个测试。你应当将测试分组成测试集,用以反应被测代码的结构。当一个测试集中的测试需要共享公用对象和子程序,可以将这些测试放入一个测试夹具(test fixture)类中。

一个测试程序可以包含多个测试集。

现在,我们介绍如何编写一个测试程序,从单个断言开始构建测试和测试集。

断言

Goolge Test中的断言是类似函数调用的宏。可以通过对被测类或函数的行为生成断言来对其进行测试。当一个断言失败是,Goolge 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字符串和 string 对象。如果一个宽字符串 (如Windows下 UNICODE 模式中的 char_t*, TCHAR* ,或std::wstring)被输入到一个断言中,在打印时它会被转换成UTF-8格式。

基本断言

以下这些断言处理基本的 true/false 条件测试。

image_thumb10

记住,当这些断言失败时,ASSERT_* 会导致一个致命错误并从当前的函数中返回,而EXPECT_* 会导致一个非致命错误,并允许函数继续进行。对于任何一个情况,一个断言都表示它所包含的测试失败。

适用环境:Linux, Windows, Mac.

二元比较

这一节介绍比较两个值的断言。

image_thumb8

当断言失败时,Google Test同时打印 val1 和 val2。在使用 ASSERT_EQ* 和 EXPECT_EQ* (和后面我们将介绍的其他同类断言)时,应当将需要测试的表达式放在 actual 的位置,将表达式的预期值放在 expected 的位置,Goolge Test的失败信息是根据这个惯例优化的。

参与比价的的值必须能够使用断言的比较操作符进行比较,否则你将得到一个编译错误。同时,为了输出到 ostream 中,值必须支持 << 操作符。所有的内置类型都满足这些要求。

这些断言可以对用户自定义类型进行操作,前提是必须定义相应的比较操作符(例如:==, <等等)。如果已定义了对应的操作符,那么推荐使用 ASSERT_*( ) 宏,因为这些宏不仅仅输出比较的结果,同时会输出两个操作数。

参数求值永远只进行一次。因此,允许参数带副作用。但是对于普通的 C/C++函数,参数的求值次序是未定义的(例如,编译器可以选择任意的顺序),因此你的代码不应当依赖于特别的参数求值顺序。

ASSERT_EQ( ) 宏可以对指针是否相等进行判断。如果将其用于两个C字符串,它测试两个字符串是否在同一个内存位置,而不是比较其内容是否相同。因此,如果想要比较C字符串(如const char*)的值(内容),需要使用稍后介绍的宏ASSERT_STREQ( )。特别的,要判断一个C字符串是NULL,需要使用 ASSERT_STREQ(NULL, c_string)。但是,要判断两个 string 对象,应当使用宏ASSERT_EQ。

本节内容中的宏同时支持窄/宽 string 对象(string 和 wstring)。

适用环境:Linux, Windows, Mac.

字符串比较

本组断言用于比较两个C字符串。如果你需要比较两个string对象,需要使用EXPECT_EQ, EXPECT_NE等等。

image_thumb4

注意,断言名称中的“CASE”是指该案例需要被忽略。

*STREQ* 和 *STRNE* 都支持宽C字符串(wchar_t*)。如果两个宽字符串比较失败,它们的值会以UTF-8窄字符串的形式被打印。

我们认为一个NULL指针和一个空字符串是不相同的。

适用环境:Linux, Windows, Mac.

简单测试

生成一个测试:

    1、使用TEST( )宏定义并命名一个测试函数,这些函数是普通的C++函数,没有返回值。
    2、在这个函数内,与任何合法的C++语句一起,使用各种Goolge Test中的断言对各种值进行检查。
    3、根据断言得到测试结果;如果测试中有一个断言失败(不论是致命还是非致命的),或者测试崩溃了,那么整个测试就是失败的。否则,认为测试成功。

TEST(test_case_name, test_name) { 
... test body ...
}

TEST( )参数从一般到特殊。第一个参数是这个测试案例的名称,第二个参数是测试案例中的测试名称。一个测试案例可以包含任意个单独的测试。一个测试的全名由它包含的测试案例和它自己的个体名称构成。不同测试案例中的测试可以有相同的个体名称。

例如,对于一个简单的整数函数:

int Factorial(int n); // Returns the factorial of 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));
}

Goolge Test根据测试集将这些测试结果分组,因此逻辑相关的的测试应当被包含在同一个测试集中;换言之,即它们的TEST ( )中的第一个声明应当相同。在上面的例子中,我们有两个测试,HandlesZeroInputHandlesPositiveInput,它们属于同一个测试集FactorialTest

适用环境:Linux, Windows, Mac.

测试夹具:对多个测试使用相同的测试数据配置

如果你发现需要写两个以上的测试来对相似的数据进行测试,你可以使用一个测试夹具(test fixture)。它与许你同时对多个不同的测试使用相同的配置。

要生成一个夹具,只需:

    1、从 ::testing::Test 继承一个类。以 protected: 或 public: 来作为类定义的主体的起始,我们将从子类来操作夹具成员。
    2、在类的内部,声明任何你计划使用的对象。
    3、如果有必要,编写一个默认构造函数或 SetUp( )函数用以为每一个测试准备对象。一个常见的错误是误把 SetUp( ) 写成 Setup( )——避免让这种事在你身上发生。
    4、如果有必要,编写一个析构函数或 TearDown( )函数来释放你在SetUp( )中获取的资源。要更进一步学习合适需要使用构造/析构函数,何时需要使用 SetUp( )/TearDown( )函数,请阅读
FAQ
    5、如果有需要,为你的测试定义用于共享的子例程。

当使用夹具时,使用 TEST_F( )来取代原先的 TEST( ),它允许你操作一个测试夹具中的对象和子例程:

TEST_F(test_case_name, test_name) { 
... test body ...
}

与TEST( )类似,第一个参数是测试集名称,但是对于 TEST_F( ),这个名称同时必须是测试夹具类名。或许你已经猜到了:_F 表示夹具(fixture)。

但是,C++宏系统不允许我们编写能够同时处理两类测试的单个宏。使用错误的宏会导致编译错误。

而且,在一个 TEST_F( ) 中使用测试夹具前必须对其进行定义,否则也将得到编译错误“错误的外部虚类声明”。

对于每一个使用 TEST_F( ) 定义的测试,Google Test将:

    1、在运行时创建一个新鲜的(fresh)测试夹具
    2、立即通过 SetUp( )函数对其进行初始化
    3、运行测试
    4、调用 TearDown( ) 进行清理工作
    5、删除测试夹具。注意,在同一个测试集中的不同测试拥有不同的测试夹具对象,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;
  ...
};

首先,定义一个夹具类。为方便起见,应当为该夹具取名为 FooTest, 这里 Foo 是被测试类的类名。

在这个示例中,TearDown( )是不需要的,因为我们不需要再每个测试结束后进行清理操作,这些操作已经在析构函数中完成了。

现在,我们将使用 TEST_F ( ) 和这个夹具来编写测试。

以上测试同时使用 ASSERT_* EXPECT_* 和断言。经验法则是,当我们在断言失败后想要测试继续暴露更多错误,那么使用 EXPECT_*。 如果断言失败后继续进行测试已经没有意义,那么使用 ASSERT_* 。例如,再 Dequeue 测试中的第二个断言是 ASSERT_TRUE (n != NULL),当我们在后面需要对指针 n 进行取值操作时,当 n 是 NULL 时,将会导致一个段错误。

当这些测试运行时,将发生以下步骤:

    1、Goolge Test 构建一个 QueueTest 对象(让我们称之为 t1)。
    2、t1.SetUp( )初始化 t1。
    3、第一个测试( IsEmptyInitially ) 对 t1 进行测试。
    4、t1.TearDown( ) 在测试结束后进行清理。
    5、t1 被销毁。
    6、以上步骤在另一个 QueueTest 对象中重复进行,而此次将运行 DequeueWorks 测试。

适用环境:Linux, Windows, Mac。

注意:Google Test将在一个测试对象构建时自动保存所有 Goolge Test 标记,在其被销毁时对其进行恢复。

调用测试

TEST( ) 和 TEST_F( ) 显示地向Goolge Test 注册其测试案例。因此,和其他C++测试框架不同,你不需要为了运行这些测试而重新列出所有已定义的测试。

在定义了所有的测试以后,可以通过 RUN_ALL_TESTS( )来运行所有的测试,当所有的测试都成功时返回0,否则返回1。注意,RUN_ALL_TESTS( )将运行所有在链接单元中的所有测试——它们可以来自不同的测试集,甚至不同的代码文件。

当 RUN_ALL_TESTS( ) 宏被调用后:

    1、保存所有Goolge Test标记的状态
    2、为第一个测试创建一个测试夹具
    3、通过 SetUp( ) 进行初始化
    4、对夹具对象运行测试
    5、通过 TearDown( )函数对夹具进行清理
    6、删除夹具
    7、重置所有Google Test标记的状态
    8、为下一个测试重复以上所有的步骤,直到所有的测试都被运行。

另外,如果测试夹具的构造函数在第2步生成一个致命错误,那么第3 - 5步就没有必要了,因此将会被跳过。类似的,如果第3步产生一个致命错误,第4部将会被跳过。

重要。据不能忽略 RUN_ALL_TESTS( ) 的返回值,否则 gcc 将会给出一个编译错误。这个设计的依据是,自动化测试服务通过返回值来判断一个测试是否通过,而不是通过其 stdout/stderr 输出。因此,main( )函数必须返回 RUN_ALL_TESTS( )的返回值。

同样的,你只能调用 RUN_ALL_TESTS( )一次,调用该宏多余一次会与一些高级的Goolge Test特性(例如,线程安全死亡测试(thread-safe death tests))冲突,因此不受支持。

适用环境:Linux, Windows, Mac。

编写 main( ) 函数

可以通过这个模板开始:

#include "this/package/foo.h" 
#include
 
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::InitGoolgeTest( )为Google Test标记进行命令行语法分析,去除所有识别的标记。这允许用户通过不同的标记对测试程序的行为进行控制,我们将在高级指南中对其进行介绍。必须在调用RUN_ALL_TESTS( )前调用该函数,否则标记将不能合理地被初始化。

在Windows环境下,InitGoolgeTest( )支持宽字符串,因此,该函数也能够在UNICODE模式下编译的程序中使用。

或许你会觉得编写所有这些main( )函数是否工作量太大?我们极为赞同你的观点,这也是为什么Google Test提供一个基本的main( )函数实现。如果它符合你的需要,那么只需将你的测试和gtest_main库进行链接就可以了。

Visual C++用户注意事项

如果你测试放入一个库中,你的main( )函数在另一个库或在.exe文件中,那些测试将不会被运行。原因在于Visual C++的一个bug,当你定义你的测试时,Goolge Test创建一些静态对象来注册它们。这些对象并不会在别处被引用,但是其构造函数仍应当运行。当 Visual C++ 链接器发现库中没有任何东西在别处被引用,将会把该库踢出掉。为了保证连接器不会丢弃该库,你必须在主程序中引用包含测试的库。下面是一个如何操作例子。在你库代码的任意位置声明一个函数:

__declspec(dllexport) int PullInMyLibrary() { return 0; }

如果你将测试放在一个静态链接库(非 DLL)中,那么不需要__declspec(dllexport)。现在,在主程序中添加一段代码调用该函数:

这样将保证你的测试被引用,并且在程序启动阶段进行注册。

此外,如果你在一个静态库中定义测试,需要在主程序的链接器选项中添加 /OPT:NOREF 选项。如果使用MSVC++ IDE,转到生成 .ext 的工程属性选项卡,project properties/Configuration Properties/Linker/Optimization,设置引用设置为Keep Unreferenced Data (/OPT:NOREF)。这样将保证 Visual C++ 链接器不会从最后的可执行文件中丢弃由你的测试生成的孤立的符号。

此外,还有一个陷阱需要注意。如果将 Google Test作为一个静态库使用(这也是 gtest.vcproj 中定义的使用方式),你的测试也必须在一个静态库中。如果你必须将它们放在一个DLL中,你必须设置将Goolge Test也编译成一个DLL。否则,你的测试将不能正确运行或者无法运行。宗旨就是:想让你的生活更轻松 ——千万不要在库中编写测试。

从这里出发

恭喜!你已经学习了Google Tesst的基础知识。现在你可以编写并运行 Google Test 测试了,阅读一些示例代码,或者继续学习高级指南,在高级指南中将介绍更多有用的Goolge Test特性。

缺陷和不足

Google Test是线程安全的。在有 ptheads 库的系统上,应用是线程安全的。目前,在其它系统中( 如  Windows),从两个线程中并发使用Google Test 断言是不安全的。在大多数测试中,这并不会出现问题,因为通常情况下断言都是在主线程中完成的。如果你需要帮助,你可以为自己的平台实现gtest-port.h中必需的同步原语。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值