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

科技资讯 投稿 9800 0 评论

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

theme: channing-cyan

*以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/RIztusI3uKRnoHVf0sloeg

本文上接《C++ 测试框架 GoogleTest 初学者入门篇 乙》,欢迎关注公众号【ENG八戒】查看更多精彩内容。


断言

googletest 的断言是一组宏定义。分为 ASSERT_* 和 EXPECT_* 两种。

ASSERT_EQ(1, 2;

EXPECT_EQ(1, 2;

上面用到的两个断言都是比较输入的数据是否相等。主要区别是,ASSERT_* 在失败时终止程序运行,EXPECT_* 在失败时不会终止程序运行,但是都会返回错误信息。因而测试使用 EXPECT_* 可以发现更多的问题而不会打断测试流程。

另外,googletest 在断言失败后除了可以返回标准错误信息,还可以附带返回自定义错误信息,使用操作符 << 添加自定义错误信息。

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

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

任何可以传递给 ostream 的数据都可以作为自定义错误信息传递给断言,比如 C 字符串、string对象。

https://google.github.io/googletest/reference/assertions.html

如何测试

简单的测试一般推荐使用 TEST 宏来定义单个测试。

TEST(test_suite_name, test_name {
  // test body
}

test_suite_name 是测试套件名,test_name 是单个测试的名称,书写时都应该符合 C++ 的标识符规范,而且不能包含有下划线_。更详细的命名规范可以查看下面的链接

那么 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 宏定义输入的测试套件名和测试名都不能为空。

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 里什么是测试夹具?

需要用到测试夹具的测试一般推荐使用 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( 用于卸载配置。

#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 派生类,并且测试套件名就是测试夹具类名。

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_ 入队两个数据。

接着调用 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 类对象,分别创建而且不共用,所以数据不会相互影响。

第二个测试 DequeueWorks,执行流程与上一个类似。其中测试内容包含有 ASSERT_* 类别的断言,这种断言在失败后除了会返回失败信息,还会终止测试程序。如果断言失败之后的测试已没有意义,那么适合使用 ASSERT_* 类别的断言。

测试调用过程

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

InitGoogleTest( 可以对程序的输入命令执行解析,基于这点可以通过命令行的方式控制测试框架的运行。

    InitGoogleTest( 初始化测试框架。
  1. RUN_ALL_TESTS( 启动测试。
  2. 查找测试套件内的测试。
  3. 保存配置标志。
  4. 创建 QueueTest 实例。
  5. 调用 QueueTest 实例的 SetUp( 初始化数据配置。
  6. 执行测试。
  7. 调用 QueueTest 实例的 TearDown( 卸载数据配置。
  8. 恢复配置标志。
  9. 重复第 3 步,直到所有测试执行完毕,

RUN_ALL_TESTS( 返回 0 表示成功,否则失败。只能在主线程里调用 RUN_ALL_TESTS(。


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

学习可以等,时间不等人!

关注我,带你学习编程领域更多核心技能!

编程笔记 » C++ 测试框架 GoogleTest 初学者入门篇 丙

赞同 (47) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽