赞
踩
什么是断言?断言是用来对表达式执行比较的代码块,调用时类似函数。当表达式一致时,断言返回成功,否则失败。
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 还提供了其它类型的断言用于协助测试,比如显式成功或失败、布尔类型断言、字符串比较断言等,详情可以前往官网查看手册。
前面提到在 googletest 中,测试的范围分为测试套件和单个测试。测试程序可以包含多个测试套件,一个测试套件可以包含多个测试。
简单的测试一般推荐使用 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 宏定义输入的测试套件名和测试名都不能为空。
然后基于 ::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() 可以对程序的输入命令执行解析,基于这点可以通过命令行的方式控制测试框架的运行。
继续以上面的代码为例,大致流程如下
RUN_ALL_TESTS() 返回 0 表示成功,否则失败。只能在主线程里调用 RUN_ALL_TESTS()。
在一般的测试里,如果在测试运行之前不需要做一些自定义的事情,而且这些事情无法在测试夹具和测试套件的框架中表达时,main 函数这部分其实都一样,那么 googletest 就在库 gtest_main 里提供了一个很方便的入口点,也就是帮你提前写好了 main 函数,你可以省去这部分,编译的时候记得链接库 gtest_main 即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。