*以下内容为自己的学习笔记,如需求转载,请声明原文链接 微信大众号「ENG八戒」mp.weixin.qq.com/s/RIztusI3u…

C++ 测试框架 GoogleTest 初学者入门篇 丙

开发者尽管首要负责工程里的开发任务,可是每个开发完毕的功用都是需求开发者自测通过的,所以常常会听到开发者提起单元测验的论题。那么今日我就带大伙一起来看看大名鼎鼎的谷歌 C++ 测验结构 GoogleTest。

本文上接《C++ 测验结构 GoogleTest 初学者入门篇 乙》,欢迎重视大众号【ENG八戒】检查更多精彩内容。


断语

什么是断语?断语是用来对表达式履行比较的代码块,调用时相似函数。当表达式共一起,断语回来成功,不然失利。

googletest 的断语是一组宏界说。分为 ASSERT_* 和 EXPECT_* 两种。

比方

ASSERT_EQ(1, 2);
EXPECT_EQ(1, 2);

上面用到的两个断语都是比较输入的数据是否相等。首要差异是,ASSERT_* 在失利时终止程序运转,EXPECT_* 在失利时不会终止程序运转,可是都会回来过错信息。因此测验运用 EXPECT_* 能够发现更多的问题而不会打断测验流程。

那么 ASSERT_* 断语失利时,跟在其后的句子会被疏忽履行,假如其间包括对资源的释放,那么就有会出现资源泄漏的问题,断语失利报错信息会附带有堆检查过错。这时出现的资源泄漏问题,真的有必要修复码?看具体状况而定。

别的,googletest 在断语失利后除了能够回来标准过错信息,还能够附带回来自界说过错信息,运用操作符 << 添加自界说过错信息。

ASSERT_EQ(1, 2) << "1 is not equal to 2";
EXPECT_EQ(1, 2) << "1 is not equal to 2";

任何能够传递给 ostream 的数据都能够作为自界说过错信息传递给断语,比方 C 字符串、string目标。

那么,测验的根本手法便是运用断语,除了判别型的断语之外,googletest 还供给了其它类型的断语用于帮忙测验,比方显式成功或失利、布尔类型断语、字符串比较断语等,详情能够前往官网检查手册。

google.github.io/googletest/…

怎么测验

前面说到在 googletest 中,测验的规模分为测验套件和单个测验。测验程序能够包括多个测验套件,一个测验套件能够包括多个测验。

简略的测验一般推荐运用 TEST 宏来界说单个测验。

一般的运用办法如下

TEST(test_suite_name, test_name) {
  // test body
}

test_suite_name 是测验套件名,test_name 是单个测验的名称,书写时都应该符合 C++ 的标识符标准,并且不能包括有下划线_。更具体的命名标准能够检查下面的链接

google.github.io/styleguide/…

那么 TEST 宏究竟代表着什么?一起来看看 TEST 宏界说的源代码

#define GTEST_STRINGIFY_HELPER_(name, ...) #name
#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )
#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test
#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()
#define GTEST_TEST(test_suite_name, test_name)             \
  GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
              ::testing::internal::GetTestTypeId())
#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)

这么多预界说处理,无妨尝试代入上面的一般运用办法,然后打开一下,打开如下

static_assert(sizeof("test_suite_name") > 1,
              "test_suite_name must not be empty");
static_assert(sizeof("test_name") > 1,
              "test_name must not be empty");
class test_suite_name_test_name_Test : public ::testing::Test {
  public:
  test_suite_name_test_name_Test() = default;
  ~test_suite_name_test_name_Test() override = default;
  test_suite_name_test_name_Test(const test_suite_name_test_name_Test &) = delete;
  test_suite_name_test_name_Test & operator=(
      const test_suite_name_test_name_Test &) = delete; /* NOLINT */
  test_suite_name_test_name_Test
  (test_suite_name_test_name_Test &&) noexcept = delete;
  test_suite_name_test_name_Test & operator=(
      test_suite_name_test_name_Test &&) noexcept = delete; /* NOLINT */
  private:
  void TestBody() override;
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;
};
::testing::TestInfo* const test_suite_name_test_name_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        "test_suite_name", "test_name", nullptr, nullptr,
        ::testing::internal::CodeLocation(__FILE__, __LINE__),
        ::testing::internal::GetTestTypeId(),
        ::testing::internal::SuiteApiResolver<
            parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),
        ::testing::internal::SuiteApiResolver<
            parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),
        new ::testing::internal::TestFactoryImpl<test_suite_name_test_name_Test>);
void test_suite_name_test_name_Test::TestBody() {
  // test body
}

从打开后的代码,能够看到有一堆代码,最开端有两个断语 static_assert 用来判别输入的测验套件名和测验名长度是否大于1,所以要求 TEST 宏界说输入的测验套件名和测验名都不能为空。

然后基于 ::testing::Test 派生了一个类,类名是测验套件名和测验名串接后再在末尾加上 _Test。类内声明重写 TestBody() 办法。

TEST 宏界说后面的 {} 用于界说派生类的成员办法 TestBody() 的函数体,内部填写标准 C++ 的有效句子作为测验主体,当然也包括调用 googletest 供给的模块内容,注意这个代码块是没有回来值的。代码块履行的断语失利时,或许代码崩溃,则测验 test_name 失利,不然成功。

再来看个例子

int square(const int a)
{
  // ...
}
TEST(SquareTest, PositiveNos) { 
    ASSERT_EQ(0, square(0));
    ASSERT_EQ(36, square(6));
    ASSERT_EQ(324, square(18));
}
TEST(SquareTest, NegativeNos) {
    ASSERT_EQ(1, square(-1));
    ASSERT_EQ(100, square(-10));
}

上面界说了两个测验 PositiveNos 和 NegativeNos,都归于测验套件 SquareTest。

googletest 在规划时就指定通过测验套件来汇总测验成果,所以验证同一个逻辑功用的测验应该界说在同一个测验套件内。

测验夹具

在 googletest 里什么是测验夹具?

测验夹具这个概念是为了解决当你的同一个逻辑功用测验里,有多个测验共用测验数据或许装备的问题。

需求用到测验夹具的测验一般推荐运用 TEST_F 宏来界说单个测验。

一般的运用办法如下

TEST_F(FixtureTest, test_name) {
  // test body
}

不过,TEST_F 宏的第一个输入参数不仅仅是测验套件名称,一起也是测验夹具类名。这个测验夹具类需求自己基于类 ::testing::Test 派生完成。

class FixtureTest : public testing::Test {
protected:
void SetUp() override { ... }
void TearDown() override { ... }
// custom data
};

共用的测验数据或许装备就在这个派生类里添加即可。SetUp() 用于初始化数据和装备,TearDown() 用于卸载装备。

那么 TEST_F 宏究竟代表着什么,和 TEST 宏的差异在哪?一起来看看 TEST_F 宏界说的源代码

#define GTEST_STRINGIFY_HELPER_(name, ...) #name
#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )
#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test
#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()
#define GTEST_TEST_F(test_fixture, test_name)        \
  GTEST_TEST_(test_fixture, test_name, test_fixture, \
              ::testing::internal::GetTypeId<test_fixture>())
#define TEST_F(test_fixture, test_name) GTEST_TEST_F(test_fixture, test_name)

这么多预界说处理,手痒代入一般的运用办法然后打开一下,打开如下

static_assert(sizeof("FixtureTest") > 1,
              "FixtureTest must not be empty");
static_assert(sizeof("test_name") > 1,
              "test_name must not be empty");
class FixtureTest_test_name_Test : public FixtureTest {
  public:
  FixtureTest_test_name_Test() = default;
  ~FixtureTest_test_name_Test() override = default;
  FixtureTest_test_name_Test(const FixtureTest_test_name_Test &) = delete;
  FixtureTest_test_name_Test & operator=(
      const FixtureTest_test_name_Test &) = delete; /* NOLINT */
  FixtureTest_test_name_Test
  (FixtureTest_test_name_Test &&) noexcept = delete;
  FixtureTest_test_name_Test & operator=(
      FixtureTest_test_name_Test &&) noexcept = delete; /* NOLINT */
  private:
  void TestBody() override;
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;
};
::testing::TestInfo* const FixtureTest_test_name_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        #FixtureTest, #test_name, nullptr, nullptr,
        ::testing::internal::CodeLocation(__FILE__, __LINE__),
        ::testing::internal::GetTypeId<FixtureTest>(),
        ::testing::internal::SuiteApiResolver<
            FixtureTest>::GetSetUpCaseOrSuite(__FILE__, __LINE__),
        ::testing::internal::SuiteApiResolver<
            FixtureTest>::GetTearDownCaseOrSuite(__FILE__, __LINE__),
        new ::testing::internal::TestFactoryImpl<FixtureTest_test_name_Test>);
void FixtureTest_test_name_Test::TestBody() {
  // test body
}

从打开后的代码来看,TEST_F 和 TEST 完成根本相似,那么运用时要遵从的规矩也是一样的,除了需求传入自界说的基于 ::testing::Test 派生类,并且测验套件名便是测验夹具类名。

举个例子,有个模板类 Queue 的逻辑功用需求测验,它完成了 FIFO 的数据队列办理。

template <typename E>  // E 是元素类型
class Queue {
 public:
  Queue();
  void Enqueue(const E& element); // 数据入队
  E* Dequeue();  // 数据出队,假如队列为空则回来 NULL
  size_t size() const;  // 队列数据长度
  ...
};

然后需求基于 ::testing::Test 派生一个测验夹具类 QueueTest

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_;
};

夹具类 QueueTest 内界说了三个队列数据目标。SetUp() 内对数据目标初始化,q0_ 保持为空,q1_ 入队一个数据,q2_ 入队两个数据。

为什么不完成 TearDown() 呢?TearDown() 原本的规划意图是卸载装备,不是刚好能够用来整理数据吗?是的,确实能够,不过这儿有个更好的选择,便是运用类析构函数来对队列清空。这儿有个主张便是,能用析构函数处理的,尽量用析构函数代替 TearDown()。由于用析构函数能够保证被调用并且调用的顺序不会乱,但不是说一切状况都主张用析构函数代替 TearDown(),这儿不打开了。

接着调用 TEST_F 界说两个测验,基于测验夹具类 QueueTest,测验套件名也是 QueueTest,两个测验名别离为 IsEmptyInitially 和 DequeueWorks。

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;
}

上面的这两个测验界说,都会创立 QueueTest 类目标,别离创立并且不共用,所以数据不会相互影响。

第一个测验 IsEmptyInitially,googletest 结构会先创立 QueueTest 类目标 obj,调用 SetUp() 初始化数据和装备,履行测验。这儿只履行了一个 EXPECT_EQ 断语,EXPECT_* 类型的断语失利后会回来失利信息,不会终止测验程序,持续下一步测验。然后调用 TearDown() 整理,最终履行目标 obj 的析构函数释放资源并退出当前测验。

第二个测验 DequeueWorks,履行流程与上一个相似。其间测验内容包括有 ASSERT_* 类别的断语,这种断语在失利后除了会回来失利信息,还会终止测验程序。假如断语失利之后的测验已没有意义,那么合适运用 ASSERT_* 类别的断语。

测验调用进程

其它 C++ 测验结构在测验开端前,需求你把测验摆放出来,可是 googletest 不需求这么麻烦。 在 googletest 结构中,界说好测验后,只需求在 main 部分履行如下代码即可。

int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

InitGoogleTest() 能够对程序的输入指令履行解析,基于这点能够通过指令行的办法操控测验结构的运转。

持续以上面的代码为例,大致流程如下

  1. InitGoogleTest() 初始化测验结构。
  2. RUN_ALL_TESTS() 发动测验。
  3. 查找测验套件内的测验。
  4. 保存装备标志。
  5. 创立 QueueTest 实例。
  6. 调用 QueueTest 实例的 SetUp() 初始化数据装备。
  7. 履行测验。
  8. 调用 QueueTest 实例的 TearDown() 卸载数据装备。
  9. 康复装备标志。
  10. 重复第 3 步,直到一切测验履行完毕,

RUN_ALL_TESTS() 回来 0 表示成功,不然失利。只能在主线程里调用 RUN_ALL_TESTS()。

在一般的测验里,假如在测验运转之前不需求做一些自界说的工作,并且这些工作无法在测验夹具和测验套件的结构中表达时,main 函数这部分其实都一样,那么 googletest 就在库 gtest_main 里供给了一个很便利的入口点,也便是帮你提前写好了 main 函数,你能够省去这部分,编译的时候记住链接库 gtest_main 即可。


好了,这个系列的文章就写到这儿啦。

学习能够等,时间不等人!

重视我,带你学习编程范畴更多核心技能!