背景简介
Google Test简称Gtest,是Google开发的开源的C++单元测试框架。
初步阅读Gtest源码就会发现,Gtest中大量使用宏,宏的使用比函数更加灵活,一句看似简单的宏完全展开后已经面目全非,它可能在背后完成了许多复杂的功能。Gtest的易用性很多也来自于宏的使用。
RUN_ALL_TEST()宏
该宏是Gtest的启动宏,通常在main中调用,整个测试框架就开始运行。
该宏的定义为:
#define RUN_ALL_TESTS()
(::testing::UnitTest::GetInstance()->Run())
该宏实现并获取了一个UnitTest单例对象,并调用其Run方法。
UnitTest是测试框架的主体对象,生存周期为整个测试程序。
其Run()方法定义为(节选)
int UnitTest::Run() {
//初始化配置相关,已省略
...
return internal::HandleExceptionsInMethodIfSupported(
impl(),
&internal::UnitTestImpl::RunAllTests,
'auxiliary test code (environments or event listeners)') ? 0 : 1;
}
该方法初始化一些配置,并且调用HandleExceptionsInMethodIfSupported方法,该方法返回一个bool表示测试的执行结果。
HandleExceptionsInMethodIfSupported是一个模板函数,其声明如下:
template <class T, typename Result> |
Result HandleExceptionsInMethodIfSupported( |
T* object, Result (T::*method)(), const char* location) ; |
HandleExceptionsInMethodIfSupported的定义不再展开,简而言之,就是调用第二个参数的方法,也就是UnitTestImpl::RunAllTests()。
UnitTestImpl::RunAllTests()基本定义了整个测试的大体流程,完整定义如下:
// Runs all tests in this UnitTest object, prints the result, and // returns true if all tests are successful. If any exception is // thrown during a test, the test is considered to be failed, but the // rest of the tests will still be run. // // When parameterized tests are enabled, it expands and registers // parameterized tests first in RegisterParameterizedTests(). // All other functions called from RunAllTests() may safely assume that // parameterized tests are ready to be counted and run. bool UnitTestImpl::RunAllTests() { |
// True if Google Test is initialized before RUN_ALL_TESTS() is called. |
const bool gtest_is_initialized_before_run_all_tests = GTestIsInitialized(); |
|
// Do not run any test if the --help flag was specified. |
if (g_help_flag) |
return true; |
|
// Repeats the call to the post-flag parsing initialization in case the |
// user didn't call InitGoogleTest. |
PostFlagParsingInit(); |
|
// Even if sharding is not on, test runners may want to use the |
// GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding |
// protocol. |
internal::WriteToShardStatusFileIfNeeded(); |
|
// True if we are in a subprocess for running a thread-safe-style |
// death test. |
bool in_subprocess_for_death_test = false; |
|
#if GTEST_HAS_DEATH_TEST |
in_subprocess_for_death_test = |
(internal_run_death_test_flag_.get() != nullptr); |
# if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) |
if (in_subprocess_for_death_test) { |
GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_(); |
} |
# endif // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_) |
#endif // GTEST_HAS_DEATH_TEST |
|
const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, |
in_subprocess_for_death_test); |
|
// Compares the full test names with the filter to decide which |
// tests to run. |
const bool has_tests_to_run = FilterTests(should_shard |
? HONOR_SHARDING_PROTOCOL |
: IGNORE_SHARDING_PROTOCOL) > 0; |
|
// Lists the tests and exits if the --gtest_list_tests flag was specified. |
if (GTEST_FLAG(list_tests)) { |
// This must be called *after* FilterTests() has been called. |
ListTestsMatchingFilter(); |
return true; |
} |
|
random_seed_ = GTEST_FLAG(shuffle) ? |
GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0; |
|
// True if at least one test has failed. |
bool failed = false; |
|
TestEventListener* repeater = listeners()->repeater(); |
|
start_timestamp_ = GetTimeInMillis(); |
repeater->OnTestProgramStart(*parent_); |
|
// How many times to repeat the tests? We don't want to repeat them |
// when we are inside the subprocess of a death test. |
const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat); |
// Repeats forever if the repeat count is negative. |
const bool gtest_repeat_forever = repeat < 0; |
for (int i = 0; gtest_repeat_forever || i != repeat; i++) { |
// We want to preserve failures generated by ad-hoc test |
// assertions executed before RUN_ALL_TESTS(). |
ClearNonAdHocTestResult(); |
|
const TimeInMillis start = GetTimeInMillis(); |
|
// Shuffles test suites and tests if requested. |
if (has_tests_to_run && GTEST_FLAG(shuffle)) { |
random()->Reseed(static_cast<UInt32>(random_seed_)); |
// This should be done before calling OnTestIterationStart(), |
// such that a test event listener can see the actual test order |
// in the event. |
ShuffleTests(); |
} |
|
// Tells the unit test event listeners that the tests are about to start. |
repeater->OnTestIterationStart(*parent_, i); |
|
// Runs each test suite if there is at least one test to run. |
if (has_tests_to_run) { |
// Sets up all environments beforehand. |
repeater->OnEnvironmentsSetUpStart(*parent_); |
ForEach(environments_, SetUpEnvironment); |
repeater->OnEnvironmentsSetUpEnd(*parent_); |
|
// Runs the tests only if there was no fatal failure or skip triggered |
// during global set-up. |
if (Test::IsSkipped()) { |
// Emit diagnostics when global set-up calls skip, as it will not be |
// emitted by default. |
TestResult& test_result = |
*internal::GetUnitTestImpl()->current_test_result(); |
for (int j = 0; j < test_result.total_part_count(); ++j) { |
const TestPartResult& test_part_result = |
test_result.GetTestPartResult(j); |
if (test_part_result.type() == TestPartResult::kSkip) { |
const std::string& result = test_part_result.message(); |
printf('%s', result.c_str()); |
} |
} |
fflush(stdout); |
} else if (!Test::HasFatalFailure()) { |
for (int test_index = 0; test_index < total_test_suite_count(); |
test_index++) { |
GetMutableSuiteCase(test_index)->Run(); |
} |
} |
|
// Tears down all environments in reverse order afterwards. |
repeater->OnEnvironmentsTearDownStart(*parent_); |
std::for_each(environments_.rbegin(), environments_.rend(), |
TearDownEnvironment); |
repeater->OnEnvironmentsTearDownEnd(*parent_); |
} |
|
elapsed_time_ = GetTimeInMillis() - start; |
|
// Tells the unit test event listener that the tests have just finished. |
repeater->OnTestIterationEnd(*parent_, i); |
|
// Gets the result and clears it. |
if (!Passed()) { |
failed = true; |
} |
|
// Restores the original test order after the iteration. This |
// allows the user to quickly repro a failure that happens in the |
// N-th iteration without repeating the first (N - 1) iterations. |
// This is not enclosed in 'if (GTEST_FLAG(shuffle)) { ... }', in |
// case the user somehow changes the value of the flag somewhere |
// (it's always safe to unshuffle the tests). |
UnshuffleTests(); |
|
if (GTEST_FLAG(shuffle)) { |
// Picks a new random seed for each iteration. |
random_seed_ = GetNextRandomSeed(random_seed_); |
} |
} |
|
repeater->OnTestProgramEnd(*parent_); |
|
if (!gtest_is_initialized_before_run_all_tests) { |
ColoredPrintf( |
COLOR_RED, |
'IMPORTANT NOTICE - DO NOT IGNORE:' |
'This test program did NOT call ' GTEST_INIT_GOOGLE_TEST_NAME_ |
'() before calling RUN_ALL_TESTS(). This is INVALID. Soon ' GTEST_NAME_ |
' will start to enforce the valid usage. ' |
'Please fix it ASAP, or IT WILL START TO FAIL.'); // NOLINT |
#if GTEST_FOR_GOOGLE_ |
ColoredPrintf(COLOR_RED, |
'For more details, see http://wiki/Main/ValidGUnitMain.'); |
#endif // GTEST_FOR_GOOGLE_ |
} |
|
return !failed; |
} |
其中浅蓝色区域的代码是一个for循环,执行逐个Test Suite测试。而深蓝色部分是一个内层for循环,执行当前Test Suite内的每个Test Case测试。
其中repeater是一个指针,指向TestEventListener接口类。TestEventListener接口类有一系列OnXXX()回调方法。repeater在不同的执行阶段去调用这些OnXXX()方法。
我们可以看到这些回调函数的执行顺序:
基本配置完成后:
OnTestProgramStart()
每个TestSuite内:{
OnTestIterationStart()
如果有测试需要执行{
OnEnvironmentsSetUpStart()
逐个环境搭建完成
OnEnvironmentsSetUpEnd()
每个TestCase内{
执行测试
}
OnEnvironmentsTearDownStart()
逐个环境拆卸完成
OnEnvironmentsTearDownEnd()
}
OnTestIterationEnd()
}
OnTestProgramEnd()
对于测试用例的执行时如何在其中开展的本文不继续追踪。
TEST(),TEST_F()宏
TEST_F()和TEST()都是Gtest的中的主要宏,用于定义一项测试。他们两都有两个参数,使用的时候看起来好像差不多,都是定义测试的名称和类型,但是其实是具有不同意义的。在宏定义中参数名分别为TEST(test_suite_name , test_name)和TEST_F(test_fixture , test_name)。
TEST(test_suite_name , test_name)是用于定义一项测试,第一个参数是suite的名字,一个suite通常对应一个测试对象,而第二个参数是case的名字,也就是一条测试用例。在官方文档中强调了test_suite_name的命名规范,必须以Test结尾。
TEST_F(test_fixture , test_name)是使用fixture来定义一项测试,fixture其实也是一个suite,与suite不同的是它需要我们提前定义这个fixture类,也就是说这里的test_fixture应当是我们已经定义的一个类名,这个类名也是我们的test_suite_name。fixture类应当继承testing::Test类,并重写SetUp、TearDown方法,这两个方法会分别在fixture执行测试的前、后执行。
以下是两个宏的第一级展开:
#define GTEST_TEST(test_suite_name, test_name) GTEST_TEST_(test_suite_name, test_name, ::testing::Test, |
::testing::internal::GetTestTypeId()) |
|
// Define this macro to 1 to omit the definition of TEST(), which |
// is a generic name and clashes with some other libraries. |
#if !GTEST_DONT_DEFINE_TEST |
#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name) |
#endif |
|
|
#define TEST_F(test_fixture, test_name) GTEST_TEST_(test_fixture, test_name, test_fixture, |
::testing::internal::GetTypeId<test_fixture>()) |
可以看到TEST()和TEST_F()宏最终都被展开为GTEST_TEST_()宏,其中主要是第三个参数不同,TEST_F()是用的我们定义的fixture类,而TEST()是用的fixture的基类testing::Test类。在testing::Test类中,SetUp和TearDown都是空函数。所以,TEST_F()和TEST()的主要区别就在于:使用TEST_F(),我们需要自定义一个fixture类继承testing::Test类,来重写回调函数SetUp()和TearDown(),而TEST()则不具有该功能,其余二者都一样。
我们继续追踪GTEST_TEST_()的宏展开,在gtest/internal/gtest-internal.h中,其定义如下:
#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id) class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) |
: public parent_class { |
public: |
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} |
|
private: |
virtual void TestBody(); |
static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_; |
GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, |
test_name)); |
}; |
|
::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() |
可以看到,在这里,GTEST_TEST_()宏被展开成了一大段类定义,这个类继承了我们定义的fixture类(或Test类,当使用TEST()时),这其中又使用了一些宏。我们继续一一展开。
首先GTEST_TEST_CLASS_NAME_是一个很简单的宏,定义如下:
#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) test_suite_name##_##test_name##_Test |
该宏的功能只是完成了一个字符串拼接的功能,将test_suite_name和test_name以’_’连接到一起,并在末尾添加”_Test”,它将作为类名。
GTEST_ATTRIBUTE_UNUSED是一个批注宏,用于声明一个变量或实例未使用。这类宏的展开会视c++编译器的类型而定,它的作用是提供信息给编译器,而我们并不需要关心。
GTEST_DISALLOW_COPY_AND_ASSIGN_()宏不允许使用复制构造函数和使用等号赋值。这个宏应当在private:中使用,其定义如下:
#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type) type(type const &) = delete; |
GTEST_DISALLOW_ASSIGN_(type) |
#define GTEST_DISALLOW_ASSIGN_(type) |
void operator=(type const &) = delete |
到这里,所有的宏都被展开了。我们观察GTEST_TEST_CLASS_NAME_这个类的定义。该类的主要成员是一个私有的TestBody()虚函数,一个TestInfo常量指针,并在类外对TestInfo指针赋值,TestBody()的函数体则是我们定义的TEST()或TEST_F()的函数体。我们看一下给TestInfo* 赋值的MakeAndRegisterTestInfo函数,它的功能是创建并注册一个TestInfo对象。它有7个参数,分别是
GTEST_API_ TestInfo* MakeAndRegisterTestInfo( |
|
const char* test_suite_name, | suite名 |
const char* name, | case名 |
const char* type_param, | 测试参数的类型,没有则为null |
const char* value_param, | 测试参数的值,没有则为null |
CodeLocation code_location, | 测试定义的代码的位置 |
TypeId fixture_class_id, | fixture类的id |
SetUpTestSuiteFunc set_up_tc, | suite的SetUp函数指针 |
TearDownTestSuiteFunc tear_down_tc, | suite的TearDown函数指针 |
TestFactoryBase* factory ); | 创建测试类对象的工厂 |
总结以上分析,简单来说就是:TEST_F()和TEST()宏展开内容实际上是一个类定义,以及完成该类的注册。在TEST_F()中该类继承我们定义的fixture类(该类继承Test类),而TEST()中该类直接继承Test类。我们定义的函数体,是这个类的成员函数TestBody()的函数体。
TEST_P()宏
TEST_P()定义一项带参数的测试,使用TEST_P()可是使用多种不同的参数进行测试,而不用为此写多份TEST。类似于TEST_F()宏,我们需要预先定义一个fixture类,而不同之处在于fixture类要继承testing::TestWithParam<T>,而不是testing::Test。TestWithParam也是Test的一个子类,T是任意一个可复制的类,若是一个指针类,那么其所指的对象的生存周期必须由我们自己负责处理。所以TEST_P()功能更多,它具有TEST_F()的所有功能。下面是TEST_P()宏的定义:
#define TEST_P(test_suite_name, test_name) class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) |
: public test_suite_name { |
public: |
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} |
virtual void TestBody(); |
|
private: |
static int AddToRegistry() { |
::testing::UnitTest::GetInstance() |
->parameterized_test_registry() |
.GetTestSuitePatternHolder<test_suite_name>( |
#test_suite_name, |
::testing::internal::CodeLocation(__FILE__, __LINE__)) |
->AddTestPattern( |
GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name), |
new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( |
test_suite_name, test_name)>()); |
return 0; |
} |
static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; |
GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name, |
test_name)); |
}; |
int GTEST_TEST_CLASS_NAME_(test_suite_name, |
test_name)::gtest_registering_dummy_ = |
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry(); |
void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody() |
该宏展开的结构类似于TEST_F()和TEST()的展开。主要是定义了一个继承fixture类的类,并完成该测试类的注册,TEST_P()的函数体是该类的成员函数TestBody()的函数体。其不同之处在于注册方式有所不同,且TestBody()方法是public的,而不是private。
这里的TestBody的内容是定义获得参数后如何执行测试,这里使用接口函数GetParam()来从参数生成器获取参数。
使用TEST_P()宏时,我们使用INSTANTIATE_TEST_SUITE_P()宏来实例化我们的带参测试,它将定义我们会使用哪些值来做测试。在INSTANTIATE_TEST_SUITE_P()中,我们可以使用Gtest提供的一系列函数(被称为参数生成器)来获取参数。
以下是INSTANTIATE_TEST_SUITE_P宏的定义:
#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...) static ::testing::internal::ParamGenerator<test_suite_name::ParamType> |
gtest_##prefix##test_suite_name##_EvalGenerator_() { |
return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_)); |
} |
static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_( |
const ::testing::TestParamInfo<test_suite_name::ParamType>& info) { |
if (::testing::internal::AlwaysFalse()) { |
::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_( |
__VA_ARGS__, |
::testing::internal::DefaultParamName<test_suite_name::ParamType>, |
DUMMY_PARAM_))); |
auto t = std::make_tuple(__VA_ARGS__); |
static_assert(std::tuple_size<decltype(t)>::value <= 2, |
'Too Many Args!'); |
} |
return ((GTEST_EXPAND_(GTEST_GET_SECOND_( |
__VA_ARGS__, |
::testing::internal::DefaultParamName<test_suite_name::ParamType>, |
DUMMY_PARAM_))))(info); |
} |
static int gtest_##prefix##test_suite_name##_dummy_ |
GTEST_ATTRIBUTE_UNUSED_ = |
::testing::UnitTest::GetInstance() |
->parameterized_test_registry() |
.GetTestSuitePatternHolder<test_suite_name>( |
#test_suite_name, |
::testing::internal::CodeLocation(__FILE__, __LINE__)) |
->AddTestSuiteInstantiation( |
#prefix, >est_##prefix##test_suite_name##_EvalGenerator_, |
>est_##prefix##test_suite_name##_EvalGenerateName_, |
__FILE__, __LINE__) |
该宏的第一个参数是该次实例化的测试名,第二个参数是suite名,第三个参数为参数生成器。该宏的内容较多,大致完成了以下内容:
1. 定义一个获取参数生成器的函数gtest_##prefix##test_suite_name##_EvalGenerator_
2. 定义一个生成参数字符串的函数gtest_##prefix##test_suite_name##_EvalGenerateName_
3. 注册该TestCase的信息。
其中parameterized_test_registry注册一个suite的信息
AddTestSuiteInstantiation注册参数生成器的信息。
参考:
————————————————————————————
BY:HW-HH