Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用

写C++难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试。其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其实现原理。(转载请指明出于breaksoftware的csdn博客)

应用

GTest将这两种方法叫做:Typed Tests和Type-Parameterized Tests。我觉得可能叫做简单模式、高级模式比较通用。先不管这些名字吧,我们看看怎么使用

简单模式(Typed Tests)

首先我们要定义一个模板类。但是为了使用GTest框架,它要继承于Test类

template <typename T>
class TypeTest : public Test {
protected:
    bool CheckData() {
        if (typeid(T) == typeid(bool)) {
            return false;
        }
        else {
            return true;
        }
    }
};

我们只实现了一个返回bool类型的模板类型判断函数CheckData。

然后我们使用下列方式定义一个类型,类型的模板参数是我们需要传递给TypeTest的模板类型

typedef testing::Types<int, long> IntegerTypes

接下来我们使用TYPED_TEST_CASE宏注册一个测试用例

TYPED_TEST_CASE(TypeTest, IntegerTypes);

最后我们使用TYPED_TEST_P定义一个测试特例

TYPED_TEST_P(TypeTest, Verify) {
    EXPECT_TRUE(CheckData());
};

如此我们可以对TypeTest<int>和TypeTest<long>进行测试。我们看下结果

[----------] 1 test from TypeTest/0, where TypeParam = int
[ RUN      ] TypeTest/0.Verify
[       OK ] TypeTest/0.Verify (2454 ms)
[----------] 1 test from TypeTest/0 (2455 ms total)

[----------] 1 test from TypeTest/1, where TypeParam = long
[ RUN      ] TypeTest/1.Verify
[       OK ] TypeTest/1.Verify (4843 ms)
[----------] 1 test from TypeTest/1 (11093 ms total)

高级模式

如果我们还要对TypeTest使用其他类型作为模板参数进行测试,可能要这么写

typedef testing::Types<float, double> FloatTypes;
TYPED_TEST_CASE(TypeTest, FloatTypes); // compile error

但是编译会报错,因为TYPED_TEST_CASE中定义的变量重定义了(之前TYPED_TEST_CASE(TypeTest, IntegerTypes);中定义的)。这个时候我们就要使用高级模式

首先我们需要声明一下测试用例类

TYPED_TEST_CASE_P(TypeTest);

然后使用TYPED_TEST_P定义一个测试实体

TYPED_TEST_P(TypeTest, Verify) {
    EXPECT_TRUE(CheckData());
};

接下来使用REGISTER_TYPED_TEST_CASE_P注册测试用例

REGISTER_TYPED_TEST_CASE_P(TypeTest, Verify);

最后使用INSTANTIATE_TYPED_TEST_CASE_P宏创建每个测试特例

INSTANTIATE_TYPED_TEST_CASE_P(IntegerCheck, TypeTest, IntegerTypes);
INSTANTIATE_TYPED_TEST_CASE_P(FloatCheck, TypeTest, FloatTypes);
INSTANTIATE_TYPED_TEST_CASE_P(BoolCheck, TypeTest, bool);

上面三行测试了三组类型,其输出是

[----------] 1 test from IntegerCheck/TypeTest/0, where TypeParam = int
[ RUN      ] IntegerCheck/TypeTest/0.Verify
[       OK ] IntegerCheck/TypeTest/0.Verify (702 ms)
[----------] 1 test from IntegerCheck/TypeTest/0 (703 ms total)

[----------] 1 test from IntegerCheck/TypeTest/1, where TypeParam = long
[ RUN      ] IntegerCheck/TypeTest/1.Verify
[       OK ] IntegerCheck/TypeTest/1.Verify (400 ms)
[----------] 1 test from IntegerCheck/TypeTest/1 (403 ms total)

[----------] 1 test from FloatCheck/TypeTest/0, where TypeParam = float
[ RUN      ] FloatCheck/TypeTest/0.Verify
[       OK ] FloatCheck/TypeTest/0.Verify (451 ms)
[----------] 1 test from FloatCheck/TypeTest/0 (453 ms total)

[----------] 1 test from FloatCheck/TypeTest/1, where TypeParam = double
[ RUN      ] FloatCheck/TypeTest/1.Verify
[       OK ] FloatCheck/TypeTest/1.Verify (403 ms)
[----------] 1 test from FloatCheck/TypeTest/1 (405 ms total)

[----------] 1 test from BoolCheck/TypeTest/0, where TypeParam = bool
[ RUN      ] BoolCheck/TypeTest/0.Verify
..\test\gtest_unittest.cc(117): error: Value of: CheckData()
  Actual: false
Expected: true
[  FAILED  ] BoolCheck/TypeTest/0.Verify, where TypeParam = bool (5440 ms)
[----------] 1 test from BoolCheck/TypeTest/0 (6633 ms total)

原理解析

简单模式

我们先从typedef testing::Types<int, long> IntegerTypes;这句开始分析。Types定义非常有意思,我截取部分来看看

struct Types0 {};

// Type lists of length 1, 2, 3, and so on.

template <typename T1>
struct Types1 {
  typedef T1 Head;
  typedef Types0 Tail;
};
template <typename T1, typename T2>
struct Types2 {
  typedef T1 Head;
  typedef Types1<T2> Tail;
};

template <typename T1, typename T2, typename T3>
struct Types3 {
  typedef T1 Head;
  typedef Types2<T2, T3> Tail;
};

......

template <typename T1 = internal::None, typename T2 = internal::None,
    typename T3 = internal::None, typename T4 = internal::None,
    typename T5 = internal::None, typename T6 = internal::None,
    typename T7 = internal::None, typename T8 = internal::None,
    typename T9 = internal::None, typename T10 = internal::None,
    typename T11 = internal::None, typename T12 = internal::None,
    typename T13 = internal::None, typename T14 = internal::None,
    typename T15 = internal::None, typename T16 = internal::None,
    typename T17 = internal::None, typename T18 = internal::None,
    typename T19 = internal::None, typename T20 = internal::None,
    typename T21 = internal::None, typename T22 = internal::None,
    typename T23 = internal::None, typename T24 = internal::None,
    typename T25 = internal::None, typename T26 = internal::None,
    typename T27 = internal::None, typename T28 = internal::None,
    typename T29 = internal::None, typename T30 = internal::None,
    typename T31 = internal::None, typename T32 = internal::None,
    typename T33 = internal::None, typename T34 = internal::None,
    typename T35 = internal::None, typename T36 = internal::None,
    typename T37 = internal::None, typename T38 = internal::None,
    typename T39 = internal::None, typename T40 = internal::None,
    typename T41 = internal::None, typename T42 = internal::None,
    typename T43 = internal::None, typename T44 = internal::None,
    typename T45 = internal::None, typename T46 = internal::None,
    typename T47 = internal::None, typename T48 = internal::None,
    typename T49 = internal::None, typename T50 = internal::None>
struct Types {
  typedef internal::Types50<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,
      T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
      T27, T28, T29, T30, T31, T32, T33, T34, T35, T36, T37, T38, T39, T40,
      T41, T42, T43, T44, T45, T46, T47, T48, T49, T50> type;
};

template <>
struct Types<internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None> {
  typedef internal::Types0 type;
};
template <typename T1>
struct Types<T1, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None> {
  typedef internal::Types1<T1> type;
};
template <typename T1, typename T2>
struct Types<T1, T2, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None, internal::None, internal::None, internal::None,
    internal::None> {
  typedef internal::Types2<T1, T2> type;
};

这段代码很长,但是其定义了我们用于罗列类型的结构体Types。它是一个递归定义,即Types3依赖于Types2,Types2依赖于Types1,Types1依赖于Types0……。每个模板类都会将自己模板列表的第一个模板别名为Head,剩下的类型别名为Tail。未来我们将看到这两个类型的使用。

我们再看下TYPED_TEST_CASE的实现

# define TYPED_TEST_CASE(CaseName, Types)   typedef ::testing::internal::TypeList< Types >::type       GTEST_TYPE_PARAMS_(CaseName)
# define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_

它只是对该测试用例的参数列表类的type做了一个别名,展开代码就是

typedef ::testing::internal::TypeList< IntegerTypes >::type gtest_type_params_TypeTest_

TypeList是个模板类,它将Types<T>别名为type

template <typename T>
struct TypeList {
  typedef Types1<T> type;
};

对应于我们的例子就是

struct TypeList<IntegerTypes> {
  typedef Types1<IntegerTypes> type;
};

于是整体展开就是

typedef ::testing::internal::Types1<IntegerTypes> gtest_type_params_TypeTest_

最后我们看下TYPED_TEST的宏实现。它和之前博文介绍的TEST宏有如下相同之处:

  • 定义了私有的虚方法TestBody
  • 执行了注册逻辑
  • 末尾声明了TestBody函数部分,便于开发者填充测试实体

相同的地方我们就不说了,我们看下不同的

# define TYPED_TEST(CaseName, TestName)   template <typename gtest_TypeParam_>   class GTEST_TEST_CLASS_NAME_(CaseName, TestName)       : public CaseName<gtest_TypeParam_> {    private:     typedef CaseName<gtest_TypeParam_> TestFixture;     typedef gtest_TypeParam_ TypeParam;     virtual void TestBody();   }; \

它继承于一个模板类,模板类的类名是我们通过TYPED_TEST传入的测试用例类。同时它将父类、模板类进行了别名操作。用我们的例子展开代码即是

template <typename T>
  class TypeTest_Verify_Test
      : public TypeTest<T> {
   private:
    typedef TypeTest<T> TestFixture;
    typedef T TypeParam;
    virtual void TestBody();
  }; 

最后一个傀儡变量被初始化

bool gtest_##CaseName##_##TestName##_registered_ GTEST_ATTRIBUTE_UNUSED_ =       ::testing::internal::TypeParameterizedTest<           CaseName,           ::testing::internal::TemplateSel<               GTEST_TEST_CLASS_NAME_(CaseName, TestName)>,           GTEST_TYPE_PARAMS_(CaseName)>::Register(              "", ::testing::internal::CodeLocation(__FILE__, __LINE__),               #CaseName, #TestName, 0); \

我们把代码展开

  bool gtest_TypeTest_Verify_registered_ GTEST_ATTRIBUTE_UNUSED_ =
      ::testing::internal::TypeParameterizedTest<TypeTest,
						::testing::internal::TemplateSel<TypeTest_Verify_Test>,
						gtest_type_params_TypeTest_
						>						 ::Register(
							"",
							::testing::internal::CodeLocation(__FILE__, __LINE__),
							'TypeTest',
							'Verify',
							0); 

我们看下TypeParameterizedTest类的Register实现

template <GTEST_TEMPLATE_ Fixture, class TestSel, typename Types>
class TypeParameterizedTest {
 public:
  // 'index' is the index of the test in the type list 'Types'
  // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase,
  // Types).  Valid values for 'index' are [0, N - 1] where N is the
  // length of Types.
  static bool Register(const char* prefix,
                       CodeLocation code_location,
                       const char* case_name, const char* test_names,
                       int index) {
    typedef typename Types::Head Type;
    typedef Fixture<Type> FixtureClass;
    typedef typename GTEST_BIND_(TestSel, Type) TestClass;

    // First, registers the first type-parameterized test in the type
    // list.
    MakeAndRegisterTestInfo(
        (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + "/"
         + StreamableToString(index)).c_str(),
        StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(),
        GetTypeName<Type>().c_str(),
        NULL,  // No value parameter.
        code_location,
        GetTypeId<FixtureClass>(),
        TestClass::SetUpTestCase,
        TestClass::TearDownTestCase,
        new TestFactoryImpl<TestClass>);

    // Next, recurses (at compile time) with the tail of the type list.
    return TypeParameterizedTest<Fixture, TestSel, typename Types::Tail>
        ::Register(prefix, code_location, case_name, test_names, index + 1);
  }
};

这段代码非常重要,我们看到核心函数MakeAndRegisterTestInfo,它将我们测试的对象加入到框架的执行队列中。具体它的原理和实现可以参看《Google Test(GTest)使用方法和源码解析——自动调度机制分析》

第12行别名了Types::Head为Type。Types是传入的模板类,以我们的例子为例,其传入的就是::testing::internal::Types1<IntegerTypes>。我们在介绍Types模板类时提到过Head别名,它是该模板类第一个模板参数类型。对应于我们的例子就是typedef testing::Types<int, long> IntegerTypes;中的int类型。

第13行使用12行别名的类型,特化了我们传入的测试用例类,即该行对应于

typedef TypeTest<int> FixtureClass;

第14行对测试特例类使用了int类型进行特化

template <GTEST_TEMPLATE_ Tmpl>
struct TemplateSel {
  template <typename T>
  struct Bind {
    typedef Tmpl<T> type;
  };
};

# define GTEST_BIND_(TmplSel, T)   TmplSel::template Bind<T>::type

即对应于TypeTest_Verify_Test<T>::type

typedef typename TypeTest_Verify_Test<T> TestClass;

如此MakeAndRegisterTestInfo函数中的参数就比较明确了。

这段代码中还有个非常重要的一个递归调用

    // Next, recurses (at compile time) with the tail of the type list.
    return TypeParameterizedTest<Fixture, TestSel, typename Types::Tail>
        ::Register(prefix, code_location, case_name, test_names, index + 1);

这次调用,最后一个模板参数传递了Types::Tail。它在编译期间将触发编译器进行类型推导,如同抽丝剥茧般,使用typedef testing::Types<int, long> IntegerTypes;中每个模板类型对TypeParameterizedTest::Register进行特化。从而可以在运行期间可以对每个类型进行注册。

高级模式

我们先看下TYPED_TEST_CASE_P宏的实现

# define TYPED_TEST_CASE_P(CaseName)   static ::testing::internal::TypedTestCasePState       GTEST_TYPED_TEST_CASE_P_STATE_(CaseName)

它定义了一个TypedTestCasePState类型的全局变量,对应于我们例子就是

  static ::testing::internal::TypedTestCasePState  gtest_typed_test_case_p_state_TypeTest_;

TypedTestCasePState类暴露了AddTestName方法用于保存测试用例和测试特例名        再看下REGISTER_TYPED_TEST_CASE_P宏的实现

# define REGISTER_TYPED_TEST_CASE_P(CaseName, ...)   namespace GTEST_CASE_NAMESPACE_(CaseName) {   typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_;   }   static const char* const GTEST_REGISTERED_TEST_NAMES_(CaseName) =       GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).VerifyRegisteredTestNames(          __FILE__, __LINE__, #__VA_ARGS__)

它使用TYPED_TEST_CASE_P定义的TypedTestCasePState类对象方法VerifyRegisteredTestNames获取已注册的测试用例名,并别名了一个类型。以上两个宏都是和测试用例名称注册有关。接下来我们看下TYPED_TEST_P的实现

# define TYPED_TEST_P(CaseName, TestName)   namespace GTEST_CASE_NAMESPACE_(CaseName) {   template <typename gtest_TypeParam_>   class TestName : public CaseName<gtest_TypeParam_> {    private:     typedef CaseName<gtest_TypeParam_> TestFixture;     typedef gtest_TypeParam_ TypeParam;     virtual void TestBody();   };   static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ =       GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(          __FILE__, __LINE__, #CaseName, #TestName);   }   template <typename gtest_TypeParam_>   void GTEST_CASE_NAMESPACE_(CaseName)::TestName<gtest_TypeParam_>::TestBody()

它和我们之前介绍的TYPED_TEST实现是相似的。不同点是:

  • 直接使用传入的测试特例名作为类名
  • 调用TYPED_TEST_CASE_P定义的TypedTestCasePState类对象AddTestName对测试用例和测试特例名进行注册
  • 将测试特例类和傀儡变量初始化过程控制在一个和测试用例名相关的命名空间中

最后我们看下INSTANTIATE_TYPED_TEST_CASE_P的实现

# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types)   bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ =       ::testing::internal::TypeParameterizedTestCase<CaseName,           GTEST_CASE_NAMESPACE_(CaseName)::gtest_AllTests_,           ::testing::internal::TypeList< Types >::type>::Register(              #Prefix,               ::testing::internal::CodeLocation(__FILE__, __LINE__),               >EST_TYPED_TEST_CASE_P_STATE_(CaseName),               #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName))

可以说,套路和简单模式的注册方式是一样的。不一样的是它调用了TypeParameterizedTestCase类的Register,而不是TypeParameterizedTest的Register。还有就是Register的第二个参数是在REGISTER_TYPED_TEST_CASE_P别名的类型。我们看下这个类型的相关代码

# define REGISTER_TYPED_TEST_CASE_P(CaseName, ...)   namespace GTEST_CASE_NAMESPACE_(CaseName) {   typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_;   } \
// Template lists of length 1, 2, 3, and so on.

template <GTEST_TEMPLATE_ T1>
struct Templates1 {
  typedef TemplateSel<T1> Head;
  typedef Templates0 Tail;
};
template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2>
struct Templates2 {
  typedef TemplateSel<T1> Head;
  typedef Templates1<T2> Tail;
};

template <>
struct Templates<NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT> {
  typedef Templates0 type;
};
template <GTEST_TEMPLATE_ T1>
struct Templates<T1, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT> {
  typedef Templates1<T1> type;
};
template <GTEST_TEMPLATE_ T1, GTEST_TEMPLATE_ T2>
struct Templates<T1, T2, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT, NoneT,
    NoneT> {
  typedef Templates2<T1, T2> type;
};

可以见得这个类型和之前的Types是类似的,用于在编译期间通过编译器推导特例出注册方法。需要注意的是这个地方推导的不是模板类的类型,而是测试特例类。我们在讲解TYPED_TEST_P时提过,宏中直接使用传入的测试特例名作为类名,这是有原因的。原因就是在这儿要一个个推导。可能这么说还不太明白,我们看下高级模式的另外一种用法

TYPED_TEST_P(TypeTest, Verify) {
    EXPECT_TRUE(CheckData());
};

TYPED_TEST_P(TypeTest, Verify2) {
    EXPECT_FALSE(CheckData());
};

REGISTER_TYPED_TEST_CASE_P(TypeTest, Verify, Verify2);

这儿的Verify和Verify2都是测试特例名,于是通过REGISTER_TYPED_TEST_CASE_P操作后,就变成

typedef ::testing::internal::Templates<Verify,Verfiy2>::type gtest_AllTests_;

最后在下面注册函数中,触发对该函数使用Verify和Verfiy2进行特化的操作。

template <GTEST_TEMPLATE_ Fixture, typename Tests, typename Types>
class TypeParameterizedTestCase {
 public:
  static bool Register(const char* prefix, CodeLocation code_location,
                       const TypedTestCasePState* state,
                       const char* case_name, const char* test_names) {
    std::string test_name = StripTrailingSpaces(
        GetPrefixUntilComma(test_names));
    if (!state->TestExists(test_name)) {
      fprintf(stderr, "Failed to get code location for test %s.%s at %s.",
              case_name, test_name.c_str(),
              FormatFileLocation(code_location.file.c_str(),
                                 code_location.line).c_str());
      fflush(stderr);
      posix::Abort();
    }
    const CodeLocation& test_location = state->GetCodeLocation(test_name);

    typedef typename Tests::Head Head;

    // First, register the first test in 'Test' for each type in 'Types'.
    TypeParameterizedTest<Fixture, Head, Types>::Register(
        prefix, test_location, case_name, test_names, 0);

    // Next, recurses (at compile time) with the tail of the test list.
    return TypeParameterizedTestCase<Fixture, typename Tests::Tail, Types>
        ::Register(prefix, code_location, state,
                   case_name, SkipComma(test_names));
  }
};

在TypeParameterizedTestCase类的Register方法中我们看到有对TypeParameterizedTest的Register的调用,于是两种方式打通了。一个测试特例下的类型推导是在TypeParameterizedTest的Register中完成的,而测试用例下不同测试特例的推导则在TypeParameterizedTestCase类的Register方法中完成的。

时间: 2024-10-20 23:12:50

Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用的相关文章

Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用

在我们设计测试用例时,我们需要考虑很多场景.每个场景都可能要细致地考虑到到各个参数的选择.比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于breaksoftware的csdn博客) EXPECT_TRUE(IsPrime(0)); EXPECT_TRUE(IsPrime(1)); EXPECT_TRUE(IsPrime(2)); ...... EXPECT_TRUE(IsPrime(9999)); 这种写法明显是不合理的.GTest框架当然

Dubbo原理和源码解析之服务暴露

一.框架设计 在官方<Dubbo 用户指南>架构部分,给出了服务调用的整体架构和流程: 另外,在官方<Dubbo 开发指南>框架设计部分,给出了整体设计: 以及暴露服务时序图: 本文将根据以上几张图,分析服务暴露的实现原理,并进行详细的代码跟踪与解析. 二.原理和源码解析 2.1 标签解析 从文章<Dubbo原理和源码解析之标签解析>中我们知道,<dubbo:service> 标签会被解析成 ServiceBean. ServiceBean 实现了 Init

rest-framework之视图和源码解析

视图和源码解析 通过使用mixin类编写视图: from rest_framework import mixins from rest_framework import generics class BookViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers

jQuery方法源码解析--jQuery($)方法(一)

jQuery方法源码解析--jQuery($)方法 注: 1.本文分析的代码为jQuery.1.11.1版本,在官网上下载未压缩版即可 2.转载请注明出处 jQuery方法: 这个方法大家都不陌生,在使用过程中,它还有另外一个名字,美元符号:$,$(...)其实就是jQuery(...); 它有很多种用法,通常都返回一个jquery对象,也可以作为$(document).ready(...);的简写形式,分析之前先看一下jQuery都有什么用法. 1.jQuery( selector [, co

erlang下lists模块sort(排序)方法源码解析(二)

上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel模块,因为我们先前主要分析的split_1_*对应的是rmergel,我们先从rmergel查看,如下 ....................................................... split_1(X, Y, [], R, Rs) -> rmergel([[Y, X

EventBus的使用和源码解析

一.基本介绍 EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件.事件传递既可用于 Android 四大组件间通讯,也可以用户异步线程和主线程间通讯等等.EventBus EventBus3.0版本有较大的更新,性能上有很大提升.这里主要介绍新版本. 传统的事件传递方式包括:Handler.BroadCastReceiver.Interface 回调,相比之下 EventBus 的优点是

Android依赖注入Dagger的使用和源码解析(上篇)

一.基本概念 依赖注入(DI)和控制反转(IOC): 依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源:而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源. 使用依赖注入可以带来以下好处: 依赖的注入和配置独立于组件之外. 因为对象是在一个独立.不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库. 依赖可以注入到一个组件中:我们可以注入这

Android 事件总线OTTO使用说明和源码解析

一.Otto简单介绍 OTTO是Square推出的库,地址:https://github.com/square/otto 先来看看otto的官方介绍 An enhanced Guava-based event bus with emphasis on Android support.Otto is an event bus designed to decouple different parts of your application while still allowing them to c

TreeSet集合的add()方法源码解析(01.Integer自然排序)

>TreeSet集合使用实例 >TreeSet集合的红黑树 存储与取出(图) >TreeSet的add()方法源码     TreeSet集合使用实例 package cn.itcast_05; import java.util.TreeSet; /* * TreeSet:能够对元素按照某种规则进行排序. * 排序有两种方式 * A:自然排序 * B:比较器排序 * * TreeSet集合的特点:排序和唯一 * * 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的