【善用工具】googletest中的/samples

善用工具
http://blog.csdn.net/aganlengzi/article/details/64921496

以下为对googletest中sample1-9的粗略阅读记录,方便查看回忆.
代码地址:https://github.com/google/googletest/tree/master/googletest/samples
如果想要使用googletest,结合docs/下的Primer来看会很快上手,同时/samples会对写测试用例提供很好地参考.

1.sample1:

test被组织成在逻辑上相关的test case,这在test case的形式上可以看出:

TEST(test_case_name, test_name) {
 ... test body ...
}

其中test_case_name 和test_name都必须满足C++的命名标准,而且不包含下划线

EXPECT_EQ(expected, actual);
EXPECT_TRUE((expected) == (actual));

是一样的效果,但是在具体的使用中,如果想要进行两个值的对比,一般使用EXPECT_EQ更好,因为,EXPECT类型的assertion在不满足的时候,会将expected和actual都打印出来,方便直观查看和调试.

按照格式写好test_case和test之后,需要在main()(如果自己实现的话)中调用RUN_ALL_TESTS(),便可以执行所有的测试用例.但是google已经帮我们以静态库的形式实现了这个工作(实际在这之前还要初始化google test环境,通过调用InitGoogleTest()函数完成).在其src/gtest_main.cc中已经实现了调用InitgoogleTest()(创建)和RUN_ALL_TESTS().那么我们使用者需要做的就是链接合格静态库就可以了.

实际这中间是不是感觉少了一个步骤?就是告诉googletest需要执行的测试用例在哪儿?也就是googletest怎么知道我们写的这些测试用例在哪儿?它就是知道,我们不用care,这正是googletest让coder只关注要测什么(content)的哲学.

2.sample2:

EXPECT_STREQ()用于c_string(以’\0’结尾的c风格字符串)的检查.
如果是string对象,判等用的还是EXPECT_EQ().

3.sample3:

介绍了test fixture.
在我理解看来像是一个普通类(因为后面还有超类super class).
用于完成不同的测试用例都依赖的共用代码的封装.
但是对于数据,fixture为每个测试用例提供的都是fresh的.

class QueueTest : public testing::Test { //使用fixture需要继承自testing::Test类
// SetUp在每个基于这个fixture的每个test执行之前执行.
virtual void SetUp() {

}
//TearDown在每个基于这个fixture的test执行之后执行.
virtual void TearDown() {

}

// Other common functions.
// 辅助函数等等.

// values
// 声明每个测试用例都需要的变量.

}

// 如果使用了fixture来测试代码,那么使用TEST_F代替TEST
TEST_F(fixture_name, test_name) {

}

比如想要测试一个队列的各种功能,队列的初始化是所有测试都需要完成的工作,此时,可以将共用初始化代码放到一起封装到fixture的SetUp()函数中.可以声明几个队列对象用作测试.
再比如每个test都需要一块内存,那么可以声明指向内存的指针,在SetUp()函数中为之申请并分配内存,在TearDown()函数中回收等等.

4.sample4:

整个sample4中的内容非常少,只是告诉我们EXPECT_EQ只会对其参数进行一次检查,不同的test之间可以有相互影响(比如同一个对象累加).

5.sample5:

sample3中介绍了普通类fixture,这个sample中介绍了超类(基类).原理和C++类似.
这个基类实际还是一个fixture,只不过还有继承自它的sub-fixture,而且,没有针对它写的test case(应该也可以有).
在例子中,这个sample中的基类fixture完成了一个定时器的操作–为每个test计时,如果超时就认为是错误.(实际就是在SetUp()和TearDown()函数中分别获得系统的当前时间并相减得出).
这里有一个疑问,如果sub-fixture中也有SetUp()函数的实现,将会产生怎样的效果?
super fixture中的SetUp()是Virtual的,如果sub-fxiture中也对virtual函数进行实现的话,应该会调用sub-fixture中的SetUp和TearDown.所以这里没有C++中的多态.如果想在已经实现了SetUp和TearDown的sub-fixture中调用super fixture中的SetUp和TearDown函数,则需要手动实现:

class SubFxiture : public SuperFixture {
 protected:
  virtual void SetUp() {
    // First, we need to set up the super fixture.
    SuperFxiture::SetUp();

    // Second, some additional setup for this fixture.
    // ... ...

  }

  // By default, TearDown() inherits the behavior of
  // SuperFxture::TearDown().  As we have no additional cleaning work
  // for SubFixture, we omit it here.
  //
  // virtual void TearDown() {
  //   SuperFxiture::TearDown();
  // }

  // Other functions and values.
  // ... ...

};

// test case for SubFixture
TEST_F(subfixture_name, test_name) {

}

其中用到了对输出添加自定义内容的方法:

EXPECT_TRUE(end_time - start_time_ <= 5) << "The test took too long.";

以上如果测试不通过,在输出原本内容的基础上还会输出用户添加的后面的字符串.

6.sample6:

介绍如何测试不同子类(OnTheFlyPrimeTable和PreCalculatedPrimeTable)对基类的接口的实现.
首先基类定义了素数的两个接口:IsPrime()和GetNextPrime(),这两个接口都被定义成了纯虚函数.两个子类分别用不同的方法将这两个接口实现,现在要测试之.
主要是想复用代码.

实现的方式感觉最主要的是用到了模板和模板的特化:通过定义函数模板CreatePrimeTable并将其特化成针对两个类的模板函数,然后在测试fixture中使用.

template <class T>
PrimeTable* CreatePrimeTable();     // 函数模板

template <>
PrimeTable* CreatePrimeTable<OnTheFlyPrimeTable>() { 
    // 模板函数1 函数模板的特化 OnTheFlyPrimeTable是一个类
    return new OnTheFlyPrimeTable;
}

template <>
PrimeTable* CreatePrimeTable<PreCalculatedPrimeTable>() { 
    // 模板函数2 函数模板的特化 PreCalculatedPrimeTable也是一个类
    return new PreCalculatedPrimeTable(10000);
}
// Then we define a test fixture class template.
template <class T>
class PrimeTableTest : public testing::Test {
 protected:
  // The ctor calls the factory function to create a prime table
  // implemented by T.
  // 当定义PrimeTableTest对象的时候,T会被给定为OnTheFlyPrimeTable或者PreCalculatedPrimeTable类型,所以可以
  // 根据上面的函数模板的偏特化进行选择编译
  PrimeTableTest() : table_(CreatePrimeTable<T>()) {}

  virtual ~PrimeTableTest() { delete table_; }

  PrimeTable* const table_;
};

因为具体的调用肯定是在google_main中完成,那么需要测试的类需要传递给这个fixture模板,所以googletest提供了两种方式进行传递:这两种方式都是在以上的基础上的后续步骤,主要是具体test case的书写和传递要测试的子类列表.都要用到其提供的types.并且test case的书写方式也有变化.
1.如果在写测试代码的时候已经知道了需要测试的所有实现接口子类的名称:
使用testing::Types<>指定子类列表和使用TYPED_TEST_CASE和TYPE_TEST书写test case和其中的test.

using testing::Types;

// To write a typed test case, first use
//
//   TYPED_TEST_CASE(TestCaseName, TypeList);
//
// to declare it and specify the type parameters.  As with TEST_F,
// TestCaseName must match the test fixture name.

// The list of types we want to test.
// 需要测试的所有实现接口子类列表
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable> Implementations;

// TYPED_TEST_CASE(fixture_name, typlistname)
TYPED_TEST_CASE(PrimeTableTest, Implementations);

// Then use TYPED_TEST(TestCaseName, TestName) to define a typed test,
// similar to TEST_F.
TYPED_TEST(PrimeTableTest, ReturnsFalseForNonPrimes) {
    // 具体的test
}

2.如果在写测试代码的时候并不知道需要测试的所有实现接口子类的名称:
使用TYPED_TEST_CASE_P和TYPED_TEST_P书写test case和其中的test.

// Then, declare the test case.  The argument is the name of the test
// fixture, and also the name of the test case (as usual).  The _P
// suffix is for "parameterized" or "pattern".
// TYPED_TEST_CASE_P(fixture_name);
TYPED_TEST_CASE_P(PrimeTableTest2);

// Next, use TYPED_TEST_P(TestCaseName, TestName) to define a test,
// similar to what you do with TEST_F.
TYPED_TEST_P(PrimeTableTest2, ReturnsFalseForNonPrimes) {
    // 具体的test
}

// 然后需要声明一遍测试用例,不知道这是为什么
// Type-parameterized tests involve one extra step: you have to
// enumerate the tests you defined:
REGISTER_TYPED_TEST_CASE_P(
    PrimeTableTest2,  // The first argument is the test case name.
    // The rest of the arguments are the test names.
    ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime);

// At this point the test pattern is done.  However, you don't have
// any real test yet as you haven't said which types you want to run
// the tests with.

以上完成了测试用例的书写,剩下的就是使用了.使用其实就是传递具体的子类来进行测试.
要用到INSTANTIATE_TYPED_TEST_CASE_P将其实例化.
正像注释中说的,上面的步骤可以是在某个头文件中已经写好的,而下面这个步骤可以是实现者在自己已知子类名称的基础上对上面测试用例的复用.
用testing::Types<>指定待测子类名称列表
用INSTANTIATE_TYPED_TEST_CASE_P把测试用例,待测列表连起来

// To turn the abstract test pattern into real tests, you instantiate
// it with a list of types.  Usually the test pattern will be defined
// in a .h file, and anyone can #include and instantiate it.  You can
// even instantiate it more than once in the same program.  To tell
// different instances apart, you give each of them a name, which will
// become part of the test case name and can be used in test filters.

// The list of types we want to test.  Note that it doesn't have to be
// defined at the time we write the TYPED_TEST_P()s.
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable>
    PrimeTableImplementations;
INSTANTIATE_TYPED_TEST_CASE_P(OnTheFlyAndPreCalculated,    // Instance name
                              PrimeTableTest2,             // Test case name
                              PrimeTableImplementations);  // Type list

感觉sample6讲的这种测试的方式不会很常用,并不会有一个基类的接口需要很多不同的实现方式进行实现吧?

7.sample7:

介绍了利用参数化方法进行接口测试的方法.
主要思路是在fixture中可以通过继承自public TestWithParam来进行参数化测试,参数可以是函数接口或者基类指针等,在fixture中可以通过GetParam得到参数并进行具体的调用测试等.
首先给出了待测试的参数(两个函数接口),应该是为了后面的统一调用方式,所以要将CreatePreCalculatedPrimeTable写成模板(指定非类型普通参数)的形式,可见最后传递方式.

typedef PrimeTable* CreatePrimeTableFunc();     // 函数类型

PrimeTable* CreateOnTheFlyPrimeTable() {
  return new OnTheFlyPrimeTable();
}

template <size_t max_precalculated>             // 模板指定非类型普通参数
PrimeTable* CreatePreCalculatedPrimeTable() {
  return new PreCalculatedPrimeTable(max_precalculated);
}
// Inside the test body, fixture constructor, SetUp(), and TearDown() you
// can refer to the test parameter by GetParam(). 
// In this case, the test parameter is a factory function
// which we call in fixture's SetUp() to create and store an instance of PrimeTable.
class PrimeTableTest : public TestWithParam<CreatePrimeTableFunc*> {    // TestWithParam是一个类模板,给定的类型是函数指针类型
 public:
  virtual ~PrimeTableTest() { delete table_; }
  virtual void SetUp() { table_ = (*GetParam())(); }    // GetParam得到函数指针,并*后运行
  virtual void TearDown() {
    delete table_;
    table_ = NULL;
  }

 protected:
  PrimeTable* table_;
};
// test case
TEST_P(PrimeTableTest, ReturnsFalseForNonPrimes) {
    // 具体test
}

然后是调用方式:
INSTANTIATE_TEST_CASE_P的第三个参数Values()中指定具体的参数.

// In order to run value-parameterized tests, you need to instantiate them,
// or bind them to a list of values which will be used as test parameters.
// You can instantiate them in a different translation module, or even
// instantiate them several times.
//
// Here, we instantiate our tests with a list of two PrimeTable object
// factory functions:
INSTANTIATE_TEST_CASE_P(
    OnTheFlyAndPreCalculated,
    PrimeTableTest,
    Values(&CreateOnTheFlyPrimeTable, &CreatePreCalculatedPrimeTable<1000>));

8.sample8:

介绍了提供参数的不同组合的方式.
这种方式是比较有用的.可以方便地覆盖不同的参数的组合情况.
示例中假设程序对两种接口都进行实现,但是根据不同的系统或者需求配置提供不同的服务方式:
其中的

bool force_on_the_fly   // true:只用第一种服务方式, false:实现第二种方式

int max_precalculated   // 第二种实现方式中的table量

是可以通过设定来告知是否需要实现PreCalculatedPrimeTable这种耗内存方式的服务的.

// Suppose we want to introduce a new, improved implementation of PrimeTable
// which combines speed of PrecalcPrimeTable and versatility of
// OnTheFlyPrimeTable (see prime_tables.h). Inside it instantiates both
// PrecalcPrimeTable and OnTheFlyPrimeTable and uses the one that is more
// appropriate under the circumstances. But in low memory conditions, it can be
// told to instantiate without PrecalcPrimeTable instance at all and use only
// OnTheFlyPrimeTable.
class HybridPrimeTable : public PrimeTable {
 public:
  HybridPrimeTable(bool force_on_the_fly, int max_precalculated)
      : on_the_fly_impl_(new OnTheFlyPrimeTable),
        precalc_impl_(force_on_the_fly ? NULL :
                          new PreCalculatedPrimeTable(max_precalculated)),
        max_precalculated_(max_precalculated) {}
  virtual ~HybridPrimeTable() {
    delete on_the_fly_impl_;
    delete precalc_impl_;
  }
// 在具体提供服务时,如果实现了快速方式则使用快速方式,否则使用第一种方式.
  virtual bool IsPrime(int n) const {
    if (precalc_impl_ != NULL && n < max_precalculated_)
      return precalc_impl_->IsPrime(n);
    else
      return on_the_fly_impl_->IsPrime(n);
  }

  virtual int GetNextPrime(int p) const {
    int next_prime = -1;
    if (precalc_impl_ != NULL && p < max_precalculated_)
      next_prime = precalc_impl_->GetNextPrime(p);

    return next_prime != -1 ? next_prime : on_the_fly_impl_->GetNextPrime(p);
  }

 private:
  OnTheFlyPrimeTable* on_the_fly_impl_;
  PreCalculatedPrimeTable* precalc_impl_;
  int max_precalculated_;
};

试想,对于上述类,想要保证其在不同情况下都能提供正常服务进行测试的话,实际上是测试force_on_the_fly的两种情况,另外可能需要对max_precalculated进行不同取值的测试,这就是相当大的工作量了.

gtest提供的解决方法和sample7中的类似,基本是带参的测试,加上其自己实现的组合功能.

using ::testing::TestWithParam;
using ::testing::Bool;
using ::testing::Values;
using ::testing::Combine;
// 比sample7中多加了Bool,Values,Combine类型

// To test all code paths for HybridPrimeTable we must test it with numbers
// both within and outside PreCalculatedPrimeTable's capacity and also with
// PreCalculatedPrimeTable disabled. We do this by defining fixture which will
// accept different combinations of parameters for instantiating a
// HybridPrimeTable instance.

// 还是带参测试,只不过这里的参数变成的tuple,继续看,实际上我们只需要将传入的参数进行组合就可以了,因为要测的就是不同输入下的类的功能.
class PrimeTableTest : public TestWithParam< ::testing::tuple<bool, int> > {
 protected:
  virtual void SetUp() {
    // This can be written as
    //
    // bool force_on_the_fly;
    // int max_precalculated;
    // tie(force_on_the_fly, max_precalculated) = GetParam();
    //
    // once the Google C++ Style Guide allows use of ::std::tr1::tie.
    //
    // 取得参数的方法:
    bool force_on_the_fly = ::testing::get<0>(GetParam());
    int max_precalculated = ::testing::get<1>(GetParam());
    table_ = new HybridPrimeTable(force_on_the_fly, max_precalculated);
  }
  virtual void TearDown() {
    delete table_;
    table_ = NULL;
  }
  HybridPrimeTable* table_;
};

// test case
TEST_P(PrimeTableTest, ReturnsFalseForNonPrimes) {
    // 具体test
}

到目前为止和sample7中的还是类似,现在fixture和test case都已经写好了,那么那些参数的组合怎样传递进去呢?

// In order to run value-parameterized tests, you need to instantiate them,
// or bind them to a list of values which will be used as test parameters.
// You can instantiate them in a different translation module, or even
// instantiate them several times.
//
// Here, we instantiate our tests with a list of parameters. We must combine
// all variations of the boolean flag suppressing PrecalcPrimeTable and some
// meaningful values for tests. We choose a small value (1), and a value that
// will put some of the tested numbers beyond the capability of the
// PrecalcPrimeTable instance and some inside it (10). Combine will produce all
// possible combinations.
INSTANTIATE_TEST_CASE_P(MeaningfulTestParameters,
                        PrimeTableTest,
                        Combine(Bool(), Values(1, 10)));

就是这句了,这样又像sample7中将fixture,和参数连接起来了.
看这个样子Combine(Bool(), Values(1, 10))这句会产生所有的组合方式.
也就是:

1. true 1
2. true 10
3. false 1
4. false 10

这个Values应该可以添加更多的数值供生成组合.这个例子很好地展示了googletest复用代码的能力了.

9.sample9:

介绍了怎样利用其提供的listener自己定制输出内容而不是默认输出.
介绍了如何利用其中的反射API来枚举每个测试用例和测试.
感觉这个sample中的内容也是很有用的:
首先要定制自己的sample类,继承自EmptyTestEventListener.

// 定制自己的监听类Listener, 是需要继承自EmptyTestEventListener的
// Provides alternative output mode which produces minimal amount of
// information about tests.
class TersePrinter : public EmptyTestEventListener {
 private:
  // Called before any test activity starts.
  // 在执行所有的测试(UnitTest级)之前执行
  virtual void OnTestProgramStart(const UnitTest& /* unit_test */) {}

  // Called after all test activities have ended.
  // 在执行所有的测试(UnitTest级)之后执行
  virtual void OnTestProgramEnd(const UnitTest& unit_test) {
    fprintf(stdout, "TEST %s\n", unit_test.Passed() ? "PASSED" : "FAILED");
    fflush(stdout);
  }

  // Called before a test starts.
  // 在执行每个TestCase里面的每个test之前执行
  virtual void OnTestStart(const TestInfo& test_info) {
    fprintf(stdout,
            "*** Test %s.%s starting.\n",
            test_info.test_case_name(),
            test_info.name());
    fflush(stdout);
  }

  // Called after a failed assertion or a SUCCEED() invocation.
  // 在每个fail的ASSERT或者EXPECT出错之后,或者调用SUCCEED之后调用
  virtual void OnTestPartResult(const TestPartResult& test_part_result) {
    fprintf(stdout,
            "%s in %s:%d\n%s\n",
            test_part_result.failed() ? "*** Failure" : "Success",
            test_part_result.file_name(),
            test_part_result.line_number(),
            test_part_result.summary());
    fflush(stdout);
  }

  // Called after a test ends.
  // 在执行每个TestCase里面的每个test之后执行
  virtual void OnTestEnd(const TestInfo& test_info) {
    fprintf(stdout,
            "*** Test %s.%s ending.\n",
            test_info.test_case_name(),
            test_info.name());
    fflush(stdout);
  }
};  // class TersePrinter

TEST(CustomOutputTest, PrintsMessage) {
  // 具体test

}

然后就是在main函数中将原来默认的监听类换成自己定制的监听类了.
这个应该是没有提供可以直接调用的接口函数,所以应该自己在main函数中实现之.
正好可以借这个sample看一下整个流程:

int main(int argc, char **argv) {
  // 这个init是必要的
  InitGoogleTest(&argc, argv);

  bool terse_output = false;
  if (argc > 1 && strcmp(argv[1], "--terse_output") == 0 )
    terse_output = true;
  else
    printf("%s\n", "Run this program with --terse_output to change the way "
           "it prints its output.");

  // 获取UnitTest实例,这应该是整个程序中的测试程序执行体
  // 这个实例上安装了默认的Listener,现在我们想要将这个Listener替换成自己的
  // 所以有了后面的步骤
  UnitTest& unit_test = *UnitTest::GetInstance();

  // If we are given the --terse_output(简化输出) command line flag, suppresses(抑制) the
  // standard output and attaches own result printer.
  // 多了一个命令行参数控制
  if (terse_output) {
    // 取得Listener实例
    TestEventListeners& listeners = unit_test.listeners();

    // Removes the default console output listener from the list so it will
    // not receive events from Google Test and won't print any output. Since
    // this operation transfers ownership of the listener to the caller we
    // have to delete it as well.

    delete listeners.Release(listeners.default_result_printer());

    // Adds the custom output listener to the list. It will now receive
    // events from Google Test and print the alternative output. We don't
    // have to worry about deleting it since Google Test assumes ownership
    // over it after adding it to the list.
    listeners.Append(new TersePrinter);
  }
  // 上面关于listener的操作感觉很流畅

  int ret_val = RUN_ALL_TESTS();

  // 利用反射API来查看测试结果
  // 层级关系从高到低是:UnitTest --> TestCase --> TestInfo
  // 感觉是获得实例后逐步向下找到最低一级的test,然后查看其testinfo

  // This is an example of using the UnitTest reflection API to inspect test
  // results. Here we discount failures from the tests we expected to fail.
  int unexpectedly_failed_tests = 0;
  for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
    const TestCase& test_case = *unit_test.GetTestCase(i);
    for (int j = 0; j < test_case.total_test_count(); ++j) {
      const TestInfo& test_info = *test_case.GetTestInfo(j);
      // Counts failed tests that were not meant to fail (those without
      // 'Fails' in the name).
      if (test_info.result()->Failed() && strcmp(test_info.name(), "Fails") != 0) {
        unexpectedly_failed_tests++;
      }
    }
  }

  // Test that were meant to fail should not affect the test program outcome.
  if (unexpectedly_failed_tests == 0)
    ret_val = 0;

  return ret_val;
}

这个可以为定制自己的输出内容服务.同时介绍了用户如何操作测试中的具体的测试用例的方法(目前貌似只是改变listener和获取信息).

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值