Google Test

谷歌原文档

本文主要参考谷歌的原文档和上面的一篇博客,主要对其内用进行了提取并加上了自己的理解,如想要更深的了解,请参考上面的资料。

Google Test 简介

1. Google Test 是什么?

  • Google Test是测试技术团队根据 Google 的特定要求和约束条件开发的测试框架。主要用于代码的测试。
  • Google Test支持C++代码的任何类型的测试(不仅仅是单元测试) 。

2. Google Test的优点

  • 测试用例之间独立且可重复。Google Test的测试用例运行在不同的对象上,以此来对测试用例之间进行隔离,以保证在一条测试用例失败时可以对其单独进行调试。
  • 测试代码易维护。Google Test将具有相关性的测试用例划分到同一个测试集合(test suite)中,在同一个测试集合中的测试用例,可以共享数据和子程序。而正是这样的划分,使测试用例之间被良好的组织起来,从而保障了代码的易维护性。
  • 测试用例可移植,可复制(跨平台)。Google Test 适用于不同的操作系统,这些操作系统可能会有不同的编译器,支持异常或不支持异常,所以Google Test测试可以使用多种不同的配置。
  • 易于debug。googletest不会在遇到第一个测试失败时就在此停止运行,而是只会停止当前的测试用例并执行下一个。也许下一个用例也会报同样的错误,但测试流程并不会因为这个错误而停止,这样就能够收集到更多得错误信息,在测试流程结束后根据信息就可以对这些错误进行统一修改。
  • 测试框架应使测试编写者摆脱内部管理的繁琐工作,并让他们专注于测试内容。googletest自动跟踪定义的所有测试,并且不需要用户枚举即可运行它们。
  • 快速高效。使用googletest,你可以在测试用例之间重用共享资源,并且只需花费一次 set-up/tear-down 的开销,而无需使测试间相互依赖(这里的set-up是指构建部署那些公共资源或者需要在测试开始前构建的环境,而tear-down是指释放那些公共资源或者恢复set-up构建的环境)。

Google Test的使用

如果想要使用Google Test,首先要学会编写断言。

1. 什么是断言

断言就是那些用来检查一个条件是否为true的语句。一个断言可以有三种结果,分别是成功(Success)、非致命失败(nonfatal failure)和致命失败(fatal failure)。当一个致命失败发生时,它将终止对应的测试用例的执行;不是致命失败,则程序会继续执行下去。

2. 不同断言造成的结果

对于一个测试点,我们有两类断言可以对它进行测试。不同的断言会对不同的用例有不同的影响。

ASSER_*这种断言会在断言失败时产生致命失败,并且终止当前测试用例。(由于这种断言在失败会立即返回,所以可能会跳过后边代码中清理(clean-up)代码,这可能会导致内存泄露)

EXPECT_*这种断言在失败时会造成非致命失败,这将不会终止当前测试用例。(这种断言出错后不会终止当前测试用例,而是继续执行。若后续的断言语句依然有不通过的,则后续的断言依然可以报出测试失败信息)

如果需要自定义失败消息,可以采用如下方式:

ASSERT_EQ(x.size(), y.size()) << "自定义的消息";
for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "自定义的消息" << i;
}

说白了就是使用<<运算符或多个该运算符序列将自定义的消息流式传输到宏中。

任何可以流式传入ostream的都可以流式传入断言宏。尤其是C字符串和string对象。如果一个宽字符串(wchar_t*,TCHAR*在Windowss上的UNICODE模式,或模式std::wstring)流式传输到一个断言,在打印时将被转换为UTF-8格式)。

3. 断言的类型

断言分为三种分别为:基本断言、二元比较断言、字符串比较断言。

(1)基本断言

基本断言也就是最基本的逻辑运算。

致命断言

非致命断言

什么条件下通过

ASSERT_TRUE(事件)

EXPECT_TRUE(事件)

事件为真

ASSERT_FALSE(事件)

EXPECT_FALSE(事件)

事件为假

注意点:

  • ASSERT开头的断言失败后产生的是致命失败,而EXPECT开头的断言失败后产生的是非致命失败。
(2)二元比较断言

二元比较断言主要用于两个值之间的比较

致命断言

非致命断言

什么条件下通过

ASSERT_EQ(value1,value2)

EXPECT_EQ(value1,value2)

value1==value2

ASSERT_NE(value1,value2)

EXPECT_NE(value1,value2)

value1!=value2

ASSERT_LT(value1,value2)

EXPECT_LT(value1,value2)

value1<value2

ASSERT_LE(value1,value2)

EXPECT_LE(value1,value2)

value1<=value2

ASSERT_GT(value1,value2)

EXPECT_GT(value1,value2)

value1>value2

ASSERT_GE(value1,value2)

EXPECT_GE(value1,value2)

value1>=value2

注意点:

  • value1和value2之间必须可以用于比较,不然会报错。
(3)字符串比较断言

该断言主要用于C字符串之间的比较,string对象可以使用EXPECT_EQ,EXPECT_NE等进行比较。

致命断言

非致命断言

什么条件下通过

ASSERT_STREQ(str1,str2)

EXPECT_STREQ(str1,str2)

字符串内容相同

ASSERT_STRNE(str1,str2)

EXPECT_STRNE(str1,str2)

字符串内容不同

ASSERT_STRCASEEQ(str1,str2)

EXPECT_STRCASEEQ(str1,str2)

字符串内容相同(忽略大小写)

ASSERT_STRCASENE(str1,str2)

EXPECT_STRCASENE(str1,str2)

字符串内容不同(忽略大小写)

注意点:

  • 断言中的"CASE"表示忽略大小写。NULL空指针和空字符串被认为是不同的。
  • STREQSTRNE也能传入宽C字符串(wchar_t*)。如果比较两个宽字符串的断言失败,字符串将会以UTF-8窄字符串的形式打印。

代码实操

下面通过代码的方式进行实践。

1. 基础测试用例

创建测试用例的步骤
  1. 使用TEST()定义一个测试用例。
  2. 在TEST()函数体中,添加合法有效的C++语句,并使用Google Test的断言去进行检验。
  3. 测试的结果是由断言决定的。该测试用例中的任一断言失败(无论是致命断言还是非致命断言),或者测试用例发生了错误,该测试用例就被认定为失败的。否侧,断言成功。
TEST(TestSuiteName, TestName) {
   /*在这里书写代码*/
}

TEST()的参数:

  • 第一个参数是测试集的名称,第二个参数是测试用例的名称,该测试用例属于第一个参数所指定的测试集。
  • 这两个名称都必须是合法的C++标识符,并且不应该包含下划线(_)。
  • 一个测试用例的完整名称由包含它的测试集和它自己的名称组成。不同测试集里的测试用例可以使用同样的名称。

现在声明一个简单的函数,并给出其测试集

int Factorial(int n);    //仅作为一个例子,假设该函数能够计算n的阶乘

// 测试0的阶乘
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(Factorial(0), 1); //如果出错,这种断言会产生非致命失败
}

// 测试正数的阶乘
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(Factorial(1), 1);
  EXPECT_EQ(Factorial(2), 2);
  EXPECT_EQ(Factorial(3), 6);
  EXPECT_EQ(Factorial(8), 40320);
}

注意点:

  • Google Test通过测试集来组织这些测试的结果,所以逻辑相关的测试用例应该被放在相同的测试集中(也就是说,逻辑相关的测试用例,TEST()的第一个参数应该是一样的)
  • 在命名测试集和测试用例时,应该遵循和命名函数和类相同的规范。

2. Test Fixtures

在实际测试的过程中,如果写了多个测试用例,且他们都需要用到相似的数据,可以使用Test Fixtures。

Test Fixtures允许给不同的测试用例重用一些相同的配置。

创建Fixtures的步骤
  1. 从::testing::Test派生一个类。类体以protected:开始,因为我们想从子类中访问fixture的成员。
  2. 在这个类中,声明一些预计要使用的对象。
  3. 如果需要的话,编写一个默认的构造函数或SetUp()函数来为每个测试用例初始化fixture中的对象(注意是SetUp()不是Setup())。
  4. 如果需要的话,编写一个析构函数或者TearDown()函数去释放你在SetUp()中分配的资源。详细的使用方法请参考这里
  5. 如果需要的话,定义一些需要共享的子程序。

使用Fixtures时,使用TEST_F()替代TEST(),因为TEST_F()可以使你访问到fixture中的对象和子程序。

TEST_F(TestFixtureName, TestName) {
  /*在这里书写代码*/
}

注意点:

  • 同TEST()一样,TEST_F()的第一个参数是测试集名称,但TEST_F()要求第一个参数必须是fixture类的类名。
  • C++的宏规则不允许开发者仅创建一个宏就可以操纵不同的测试,使用错误的宏将会导致编译错误。因此才没有同一使用TEST(),而是引入了TEST_F()。
  • 在TEST_F()中使用fixture之前必须要定义好fixture类,不然会报错(virtual outside class declaration)。

Google Test会在运行时,为每一个使用TEST_F()定义的测试用例创建一个新的fixture对象,立即调用SetUp()进行初始化,然后运行测试用例,之后会通过调用TearDown()来清理资源,最后删除fixture对象。需要注意的是,同一个测试集中不同的测试用例使用的是不同的fixture对象,Google Test总是在为下一个测试用例创建fixture之前,删除当前测试使用后的fixture。Google Test不会给多个测试用例重用同一个fixture对象。一个测试用例对fixture的任何操作不会影响到其他测试用例。

下面做一个实例

/*使用fixture测试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;
  ...
};

首先,定义一个fixture类。一般的,你应该给一个待测类名为Foo的类创建一个名为FooTest的fixture类。

class QueueTest : public ::testing::Test { //从::testing::Test派生一个类 QueueTest
 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_;
};

类声明完毕后,测试用例如下:

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);  //使用ASSERT的原因在于下面的代码需要对指针n解引用,这将会在n是NULL的时候导致段错误
  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;
}

当这些测试用例在运行时

  1. Google Test构造一个QueueTest对象(如我们可以叫它t1)。
  2. t1.SetUp()初始化t1。
  3. 第一个测试用例(IsEmptyInitially)基于t1运行。
  4. t1.TearDown()在这个测试用例结束后被调用。
  5. t1被析构。
  6. 上述步骤在一个新的QueueTest对象上重复,这次基于新的QueueTest对象(可以认为这个对象是t2)运行第二个测试用例DequeueWorks。

3. 调用测试用例

TEST()和TEST_F()隐式地把它们的测试注册给 googletest,所以,不像许多其他C++测试框架,你不必为了运行它们而一一列举它们。

在你定义完测试用例之后,你可以使用RUN_ALL_TESTS()运行它们,当所有测试成功时返回0,否则返回1。注意RUN_ALL_TESTS()运行你链接的所有测试——它们可以来自不同的测试集,甚至是不同的源文件。

当RUN_ALL_TESTS()被调用时:

  1. 保存所有googletest flags的状态。
  2. 为第一个测试用例创建fixture对象
  3. 通过SetUp()初始化fixture对象。
  4. 基于这个fixture对象运行第一个测试用例。
  5. 通过调用TearDown清理fixture对象。
  6. 删除fixture。
  7. 恢复所有googletest flags的状态。
  8. 重复以上所有步骤,知道所有测试运行完毕。

当在执行某一步时发生了致命错误,下面的所有步骤都会被跳过

4. main()函数的编写

大多数用户不需要编写main函数,只要链接gtest_main库就可以,该库提供了合适的入口点。当你想在测试用例执行之前执行一些自定义的操作,但这些操作又不能通过现有的框架去添加(如fixture),就需要编写main函数了。

你编写的main函数需要返回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 constr 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();
}

::testing::InitGoogleTest()函数解析 googletest 标记的命令行参数,并移除所有已识别的标记。这允许用户通过不同的标记控制测试程序的行为。但必须在调用RUN_ALL_TESTS()之前调用这个函数,否则标记将无法得到适当的初始化。AdvancedGuide(高级教程)中有相关的描述。

在Windows下,InitGoogleTest()同样也可以基于宽字符串使用,因此它也可以被用于以UNICODE模式编译的程序。

单纯的编写一个main函数过于麻烦。这也是Google Test 提供了一个基础的main函数实现的原因。如果它能够满足你的需求的话,仅需要将你的测试用例和库gtest_main链接就可以了。