标签:
在我们设计测试用例时,我们需要考虑很多场景。每个场景都可能要细致地考虑到到各个参数的选择。比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于breaksoftware的csdn博客)
EXPECT_TRUE(IsPrime(0)); EXPECT_TRUE(IsPrime(1)); EXPECT_TRUE(IsPrime(2)); ...... EXPECT_TRUE(IsPrime(9999));这种写法明显是不合理的。GTest框架当然也会考虑到这点,它设计了一套自动生成上述检测的机制,让我们用很少的代码就可以解决这个问题。
class Bis { public: bool Even(int n) { if (n % 2 == 0) { return true; } else { return false; } }; bool Suc(bool suc) { return suc; } };该类暴露了两个返回bool类型的方法:Even用于判断是否是偶数;Suc只是返回传入的参数。
class TestClass : public Bis, public ::testing::Test { };
class CheckBisSuc : public TestClass, public ::testing::WithParamInterface<bool> { };我们再设置一个测试特例,在特例中使用GetParam()方法获取框架指定的参数
TEST_P(CheckBisSuc, Test) { EXPECT_TRUE(Suc(GetParam())); }最后,我们使用INSTANTIATE_TEST_CASE_P宏向框架注册“定制化测试”
INSTANTIATE_TEST_CASE_P(TestBisBool, CheckBisSuc, Bool());该宏的第一个参数是测试前缀,第二个参数是测试类名,第三个参数是参数生成规则。如此我们就相当于执行了
EXPECT_TRUE(Suc(true)); EXPECT_TRUE(Suc(false));
class CheckBisEven : public TestClass, public ::testing::WithParamInterface<int> { };然后我们建立一个针对该类的测试特例
TEST_P(CheckBisEven, Test) { EXPECT_TRUE(Even(GetParam())); }最后我们可以使用Range、Values或者ValuesIn的方式指定Even的参数值
INSTANTIATE_TEST_CASE_P(TestBisValuesRange, CheckBisEven, Range(0, 9, 2)); INSTANTIATE_TEST_CASE_P(TestBisValues, CheckBisEven, Values(11, 12, 13, 14)); int values[] = {0, 1}; INSTANTIATE_TEST_CASE_P(TestBisValuesIn, CheckBisEven, ValuesIn(values)); int moreValues[] = {0,1,2,3,4,5,6,7,8,9,10}; vector<int> IntVecValues(moreValues, moreValues + sizeof(moreValues)); INSTANTIATE_TEST_CASE_P(TestBisValuesInVector, CheckBisEven, ValuesIn(IntVecValues));Range的第一个参数是起始参数值,第二个值是结束参数值,第三个参数是递增值。于是Range这组测试测试的是0、2、4、6、8这些入参。如果第三个参数没有, 则默认是递增1。
class CombineTest : public TestWithParam< ::testing::tuple<bool, int> > { protected: bool checkData() { bool suc = ::testing::get<0>(GetParam()); int n = ::testing::get<1>(GetParam()); return bis.Suc(suc) && bis.Even(n); } private: Bis bis; };然后我们定义一个(true,false)和(1,2,3,4)组合测试
TEST_P(CombineTest, Test) { EXPECT_TRUE(checkData()); } INSTANTIATE_TEST_CASE_P(TestBisValuesCombine, CombineTest, Combine(Bool(), Values(0, 1, 2, 3, 4)));如何我们便可以衍生出8组测试。我们看下部分测试结果输出
[----------] 8 tests from TestBisValuesCombine/CombineTest ...... [ RUN ] TestBisValuesCombine/CombineTest.Test/6 [ OK ] TestBisValuesCombine/CombineTest.Test/6 (0 ms) [ RUN ] TestBisValuesCombine/CombineTest.Test/7 ../samples/sample11_unittest.cc:175: Failure Value of: checkData() Actual: false Expected: true [ FAILED ] TestBisValuesCombine/CombineTest.Test/7, where GetParam() = (true, 3) (1 ms) [----------] 8 tests from TestBisValuesCombine/CombineTest (2 ms total)上例中TestBisValuesCombine/CombineTest是最终的测试用例名,Test/6和Test/7是其下两个测试特例名。
Range(begin, end[, step]) | Yields values {begin, begin+step, begin+step+step, ...}. The values do not include end. step defaults to 1. |
Values(v1, v2, ..., vN) | Yields values {v1, v2, ..., vN}. |
ValuesIn(container) and ValuesIn(begin, end) | Yields values from a C-style array, an STL-style container, or an iterator range [begin, end). container, begin, and end can be expressions whose values are determined at run time. |
Bool() | Yields sequence {false, true}. |
Combine(g1, g2, ..., gN) | Yields all combinations (the Cartesian product for the math savvy) of the values generated by the N generators. This is only available if your system provides the <tr1/tuple> header. If you are sure your system does, and Google Test disagrees, you can override it by defining GTEST_HAS_TR1_TUPLE=1. See comments in include/gtest/internal/gtest-port.h for more information. |
# define TEST_P(test_case_name, test_name) class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public test_case_name { public: GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} virtual void TestBody(); private: GTEST_DISALLOW_COPY_AND_ASSIGN_( GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); ...... void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()不同的地方便是TestBody方法由私有变成公有,还有就是类的注册
private: static int AddToRegistry() { ::testing::UnitTest::GetInstance()->parameterized_test_registry(). GetTestCasePatternHolder<test_case_name>( #test_case_name, ::testing::internal::CodeLocation( __FILE__, __LINE__))->AddTestPattern( #test_case_name, #test_name, new ::testing::internal::TestMetaFactory< GTEST_TEST_CLASS_NAME_( test_case_name, test_name)>()); return 0; } static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; ...... }; int GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::gtest_registering_dummy_ = GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \TEST_P宏暴露出来的静态变量gtest_registering_dummy_明显只是一个辅助,它的真正目的只是为了让其可以在main函数之前初始化,并在初始化函数中完成类的注册。而注册函数也是实现在TEST_P定义的类的内部,但是是个静态成员函数。
private: typedef ::std::vector<ParameterizedTestCaseInfoBase*> TestCaseInfoContainer; TestCaseInfoContainer test_case_infos_;该类还暴露了一个非常重要的方法GetTestCasePatternHolder,它用于返回一个测试用例对象指针
template <class TestCase> ParameterizedTestCaseInfo<TestCase>* GetTestCasePatternHolder( const char* test_case_name, CodeLocation code_location) { ParameterizedTestCaseInfo<TestCase>* typed_test_info = NULL; for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); it != test_case_infos_.end(); ++it) { if ((*it)->GetTestCaseName() == test_case_name) { if ((*it)->GetTestCaseTypeId() != GetTypeId<TestCase>()) { // Complain about incorrect usage of Google Test facilities // and terminate the program since we cannot guaranty correct // test case setup and tear-down in this case. ReportInvalidTestCaseType(test_case_name, code_location); posix::Abort(); } else { // At this point we are sure that the object we found is of the same // type we are looking for, so we downcast it to that type // without further checks. typed_test_info = CheckedDowncastToActualType< ParameterizedTestCaseInfo<TestCase> >(*it); } break; } } if (typed_test_info == NULL) { typed_test_info = new ParameterizedTestCaseInfo<TestCase>( test_case_name, code_location); test_case_infos_.push_back(typed_test_info); } return typed_test_info; }该方法是个模板方法,模板是我们通过TEST_P传入的测试用例类。它通过我们传入的测试用例名和代码所在行数等信息,创建一个或者返回一个已存在的ParameterizedTestCaseInfo<T>*类型的数据,其指向了符合以上信息的测试用例对象。这个对象内部保存了一系列测试特例类指针
typedef ::std::vector<linked_ptr<TestInfo> > TestInfoContainer; TestInfoContainer tests_;TEST_P宏通过该对象,调用AddTestPattern方法向测试用例对象新增当前测试特例对象
void AddTestPattern(const char* test_case_name, const char* test_base_name, TestMetaFactoryBase<ParamType>* meta_factory) { tests_.push_back(linked_ptr<TestInfo>(new TestInfo(test_case_name, test_base_name, meta_factory))); }这个过程和TEST宏的思路基本一致,不同的是它引入了很多模板。但是需要注意的是,这并不是向框架的可执行队列中插入测试用例或者测试测试特例信息的地方,这只是中间临时保存的过程。
# define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator, ...) ::testing::internal::ParamGenerator<test_case_name::ParamType> gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \这个函数非常重要,之后我们就靠它生成参数。
::std::string gtest_##prefix##test_case_name##_EvalGenerateName_( const ::testing::TestParamInfo<test_case_name::ParamType>& info) { return ::testing::internal::GetParamNameGen<test_case_name::ParamType> (__VA_ARGS__)(info); } \最后它定义了一个全局傀儡变量,在其初始化时,抢在main函数执行之前注册相关信息
int gtest_##prefix##test_case_name##_dummy_ GTEST_ATTRIBUTE_UNUSED_ = ::testing::UnitTest::GetInstance()->parameterized_test_registry(). GetTestCasePatternHolder<test_case_name>( #test_case_name, ::testing::internal::CodeLocation( __FILE__, __LINE__))->AddTestCaseInstantiation( #prefix, >est_##prefix##test_case_name##_EvalGenerator_, >est_##prefix##test_case_name##_EvalGenerateName_, __FILE__, __LINE__)可见它也是通过测试用例的类名获取我们之前通过TEST_P创建的测试用例类对象,然后调用AddTestCaseInstantiation方法,传入参数生成函数指针(参数生成器)和参数名生成函数指针。通过这些信息,将新建一个定制化(Instantiation)的测试对象——Instantiationinfo。AddTestCaseInstantiation将该定制化测试对象保存到template <class TestCase> class ParameterizedTestCaseInfo类里成员变量中。
// INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information // about a generator. int AddTestCaseInstantiation(const string& instantiation_name, GeneratorCreationFunc* func, ParamNameGeneratorFunc* name_func, const char* file, int line) { instantiations_.push_back( InstantiationInfo(instantiation_name, func, name_func, file, line)); return 0; // Return value used only to run this method in namespace scope. } typedef ::std::vector<InstantiationInfo> InstantiationContainer; InstantiationContainer instantiations_;至此,我们把所有在main函数之前执行的操作给看完了。但是仍然没有发现GTest框架是如何将这些临时信息保存到执行队列中的,更没有看到调度的代码。
GetUnitTestImpl()->PostFlagParsingInit();PostFlagParsingInit最终将会调用到
void UnitTestImpl::RegisterParameterizedTests() { #if GTEST_HAS_PARAM_TEST if (!parameterized_tests_registered_) { parameterized_test_registry_.RegisterTests(); parameterized_tests_registered_ = true; } #endif }parameterized_test_registry_就是之前在TEST_P和INSTANTIATE_TEST_CASE_P宏中使用到的::testing::UnitTest::GetInstance()->parameterized_test_registry()的返回值,我们看看RegisterTests()里干了什么
void RegisterTests() { for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); it != test_case_infos_.end(); ++it) { (*it)->RegisterTests(); } }它遍历了所有通过TEST_P保存的测试用例对象(ParameterizedTestCaseInfo<T>),然后逐个调用其RegisterTests方法
virtual void RegisterTests() { for (typename TestInfoContainer::iterator test_it = tests_.begin(); test_it != tests_.end(); ++test_it) { linked_ptr<TestInfo> test_info = *test_it;一开始它枚举了所有之前通过TEST_P保存的测试特例对象,之后都会对该对象进行操作
for (typename InstantiationContainer::iterator gen_it = instantiations_.begin(); gen_it != instantiations_.end(); ++gen_it) { const string& instantiation_name = gen_it->name; ParamGenerator<ParamType> generator((*gen_it->generator)()); ParamNameGeneratorFunc* name_func = gen_it->name_func; const char* file = gen_it->file; int line = gen_it->line; string test_case_name; if ( !instantiation_name.empty() ) test_case_name = instantiation_name + "/"; test_case_name += test_info->test_case_base_name; size_t i = 0; std::set<std::string> test_param_names;然后枚举该测试用例中所有通过INSTANTIATE_TEST_CASE_P宏保存的定制化测试对象,并准备好相关数据供之后使用。
for (typename ParamGenerator<ParamType>::iterator param_it = generator.begin(); param_it != generator.end(); ++param_it, ++i) { Message test_name_stream; std::string param_name = name_func( TestParamInfo<ParamType>(*param_it, i)); GTEST_CHECK_(IsValidParamName(param_name)) << "Parameterized test name '" << param_name << "' is invalid, in " << file << " line " << line << std::endl; GTEST_CHECK_(test_param_names.count(param_name) == 0) << "Duplicate parameterized test name '" << param_name << "', in " << file << " line " << line << std::endl; test_param_names.insert(param_name); test_name_stream << test_info->test_base_name << "/" << param_name;这段代码遍历参数生成器,并使用参数名生成器把所有参数转换成一个string类型数据,插入到待输出的内容中
MakeAndRegisterTestInfo( test_case_name.c_str(), test_name_stream.GetString().c_str(), NULL, // No type parameter. PrintToString(*param_it).c_str(), code_location_, GetTestCaseTypeId(), TestCase::SetUpTestCase, TestCase::TearDownTestCase, test_info->test_meta_factory->CreateTestFactory(*param_it)); } // for param_it } // for gen_it } // for test_it } // RegisterTestsMakeAndRegisterTestInfo函数在之前的博文中做过分析,它将所有测试用例和测试特例保存到GTest框架的可执行队列中,从而完成调度前的所有准备工作。至于调度及MakeAndRegisterTestInfo的细节可以参见《Google Test(GTest)使用方法和源码解析——自动调度机制分析》。
TEST_P(CheckBisEven, Test) { EXPECT_TRUE(Even(GetParam())); } int values[] = {0, 1}; INSTANTIATE_TEST_CASE_P(TestBisValuesIn, CheckBisEven, ValuesIn(values)); INSTANTIATE_TEST_CASE_P(TestBisValues, CheckBisEven, Values(11, 12, 13, 14));第1行将新建名为CheckBisEven的测试用例。并在该测试用例下新建并保存一个名为CheckBisEven_Test_Test的测试特例。
test_info->test_meta_factory->CreateTestFactory(*param_it)这行代码是通过类厂,新建了一个特例对象。
template <class TestCase> class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { ....... typedef typename TestCase::ParamType ParamType; ....... struct TestInfo { TestInfo(const char* a_test_case_base_name, const char* a_test_base_name, TestMetaFactoryBase<ParamType>* a_test_meta_factory) : ......, test_meta_factory(a_test_meta_factory) {} ....... const scoped_ptr<TestMetaFactoryBase<ParamType> > test_meta_factory; }; ...... }注意下ParamType,它是我们传入的模板类的一个属性,即测试用例类的属性,但是我们好像没有定义过它。其实我们是通过继承template <typename T> class WithParamInterface类来设置该属性的
template <typename T> class WithParamInterface { public: typedef T ParamType;回顾下我们测试用例类的设计
class CheckBisEven : public TestClass, public ::testing::WithParamInterface<int> { };可见该用例的ParamType就是我们指定的int。框架在不知道我们指定了哪个类型的情况下,选择了一个替代符实现之后逻辑的,这在模板类设计中经常见到。
new ::testing::internal::TestMetaFactory< GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()TestMetaFactory的定义如下
template <class TestCase> class TestMetaFactory : public TestMetaFactoryBase<typename TestCase::ParamType> { public: typedef typename TestCase::ParamType ParamType; ...... };它是个模板类,继承于另一个模板类TestMetaFactoryBase,TestMetaFactoryBase的模板是TestMetaFactory模板的ParamType属性,对应于上例就是int。TestMetaFactoryBase类是个接口类,没什么好说的。
virtual TestFactoryBase* CreateTestFactory(ParamType parameter) { return new ParameterizedTestFactory<TestCase>(parameter); }它又新建了一个模板类对象指针
template <class TestClass> class ParameterizedTestFactory : public TestFactoryBase { public: typedef typename TestClass::ParamType ParamType; explicit ParameterizedTestFactory(ParamType parameter) : parameter_(parameter) {} ..... private: const ParamType parameter_; GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); };新建的类厂对象最终会保存到TestInfo中,并在测试用例执行前被调用,从而生成对应的测试特例对象。这段逻辑在《Google Test(GTest)使用方法和源码解析——自动调度机制分析》有过分析
void TestInfo::Run() { ....... Test* const test = internal::HandleExceptionsInMethodIfSupported( factory_, &internal::TestFactoryBase::CreateTest, "the test fixture's constructor"); ....... }我们再将关注的重点放到ParameterizedTestFactory 的CreateTest方法,它先通过模板类的SetParam方法设置了参数,然后新建并返回了一个模板类对象。
virtual Test* CreateTest() { TestClass::SetParam(¶meter_); return new TestClass(); }
template <typename T> class WithParamInterface { public: typedef T ParamType; virtual ~WithParamInterface() {} // The current parameter value. Is also available in the test fixture's // constructor. This member function is non-static, even though it only // references static data, to reduce the opportunity for incorrect uses // like writing 'WithParamInterface<bool>::GetParam()' for a test that // uses a fixture whose parameter type is int. const ParamType& GetParam() const { GTEST_CHECK_(parameter_ != NULL) << "GetParam() can only be called inside a value-parameterized test " << "-- did you intend to write TEST_P instead of TEST_F?"; return *parameter_; } private: // Sets parameter value. The caller is responsible for making sure the value // remains alive and unchanged throughout the current test. static void SetParam(const ParamType* parameter) { parameter_ = parameter; } // Static value used for accessing parameter during a test lifetime. static const ParamType* parameter_; // TestClass must be a subclass of WithParamInterface<T> and Test. template <class TestClass> friend class internal::ParameterizedTestFactory; };该类保存了一个静态的全局变量parameter_,它保存了参数的指针。并通过SetParam和GetParam方法设置这个全局参数。
Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用
标签:
原文地址:http://blog.csdn.net/breaksoftware/article/details/51059583