*以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/RIztusI3uKRnoHVf0sloeg
开发者虽然主要负责工程里的开发任务,但是每个开发完毕的功能都是需要开发者自测通过的,所以经常会听到开发者提起单元测试的话题。那么今天我就带大伙一起来看看大名鼎鼎的谷歌 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 还提供了其它类型的断言用于协助测试,比如显式成功或失败、布尔类型断言、字符串比较断言等,详情可以前往官网查看手册。
https://google.github.io/googletest/reference/assertions.html
如何测试
前面提到在 googletest 中,测试的范围分为测试套件和单个测试。测试程序可以包含多个测试套件,一个测试套件可以包含多个测试。
简单的测试一般推荐使用 TEST 宏来定义单个测试。
一般的使用方式如下
TEST(test_suite_name, test_name) {
// test body
}
test_suite_name 是测试套件名,test_name 是单个测试的名称,书写时都应该符合 C++ 的标识符规范,而且不能包含有下划线_。更详细的命名规范可以查看下面的链接
https://google.github.io/styleguide/cppguide.html#Function_Names
那么 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_c