Skip to content

Latest commit

 

History

History
629 lines (510 loc) · 20.2 KB

5.2_gtest单元测试.md

File metadata and controls

629 lines (510 loc) · 20.2 KB

5.2 gtest单元测试

gtest是Google的一套用于编写C++测试的框架,可以运行在很多平台上(包括Linux、Mac OS X、Windows、Cygwin等等)。基于xUnit架构。支持很多好用的特性,包括自动识别测试、丰富的断言、断言自定义、死亡测试、非终止的失败、生成XML报告等等。

1.gtest的优点

好的测试应该有下面的这些特点,我们看看GTest是如何满足要求的。

  • 测试应该是独立的、可重复的。一个测试的结果不应该作为另一个测试的前提。GTest中每个测试运行在独立的对象中。如果某个测试失败了,可以单独地调试它。
  • 测试应该是有清晰的结构的。GTest的测试有很好的组织结构,易于维护。
  • 测试应该是可移植和可复用的。有很多代码是不依赖平台的,因此它们的测试也需要不依赖于平台。GTest可以在多种操作系统、多种编译器下工作,有很好的可移植性。
  • 测试失败时,应该给出尽可能详尽的信息。GTest在遇到失败时并不停止接下来的测试,而且还可以选择使用非终止的失败来继续执行当前的测试。这样一次可以测试尽可能多的问题。
  • 测试框架应该避免让开发者维护测试框架相关的东西。GTest可以自动识别定义的全部测试,你不需要一一列举它们。
  • 测试应该够快。GTest在满足测试独立的前提下,允许你复用共享数据,它们只需创建一次。
  • GTest采用的是xUnit架构,你会发现和JUnit、PyUnit很类似,所以上手非常快。

2.搭建测试框架

gtest下载地址: https://github.com/google/googletest

下载方法是:git clone https://github.com/google/googletest.git
安装方法是:

$ cd googletest
$ cmake .
$ make

然后在lib目录下会生成:libgmock.a libgmock_main.a libgtest.a libgtest_main.a
最后我们再sudo make install

3.测试Demo

  • 第一步:假设我们的实现两个函数:

#include "sample1.h"

// Returns n! (the factorial of n).  For negative n, n! is defined to be 1.
int Factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; i++) {
    result *= i;
  }

  return result;
}

// Returns true iff n is a prime number.
bool IsPrime(int n) {
  // Trivial case 1: small numbers
  if (n <= 1) return false;

  // Trivial case 2: even numbers
  if (n % 2 == 0) return n == 2;

  // Now, we have that n is odd and n >= 3.

  // Try to divide n by every odd number i, starting from 3
  for (int i = 3; ; i += 2) {
    // We only have to try i up to the square root of n
    if (i > n/i) break;

    // Now, we have i <= n/i < n.
    // If n is divisible by i, n is not prime.
    if (n % i == 0) return false;
  }

  // n has no integer factor in the range (1, n), and thus is prime.
  return true;
}

这两个函数定义在sample1.cc文件里,函数申明在sample1.h里:

#ifndef GTEST_SAMPLES_SAMPLE1_H_
#define GTEST_SAMPLES_SAMPLE1_H_

// Returns n! (the factorial of n).  For negative n, n! is defined to be 1.
int Factorial(int n);

// Returns true iff n is a prime number.
bool IsPrime(int n);

#endif 

  • 第二步:现在我们就是要测试Factorial和IsPrime两个函数是否正确,好了开始写我们的测试用例把。新建一个文件,命名为sample_unittest.cc,代码如下:
#include <limits.h>
#include "sample1.h"
#include "gtest/gtest.h"
namespace {

TEST(FactorialTest, Negative) {
    // This test is named "Negative", and belongs to the "FactorialTest"
    // test case.
    EXPECT_EQ(1, Factorial(-5));
    EXPECT_EQ(1, Factorial(-1));
    EXPECT_GT(Factorial(-10), 0);
}

TEST(FactorialTest, Zero) {
    EXPECT_EQ(1, Factorial(0));
}

TEST(FactorialTest, Positive) {
    EXPECT_EQ(1, Factorial(1));
    EXPECT_EQ(2, Factorial(2));
    EXPECT_EQ(6, Factorial(3));
    EXPECT_EQ(40320, Factorial(8));
}

// Tests IsPrime()
TEST(IsPrimeTest, Negative) {
  EXPECT_FALSE(IsPrime(-1));
  EXPECT_FALSE(IsPrime(-2));
  EXPECT_FALSE(IsPrime(INT_MIN));
}

TEST(IsPrimeTest, Trivial) {
  EXPECT_FALSE(IsPrime(0));
  EXPECT_FALSE(IsPrime(1));
  EXPECT_TRUE(IsPrime(2));
  EXPECT_TRUE(IsPrime(3));
}

TEST(IsPrimeTest, Positive) {
  EXPECT_FALSE(IsPrime(4));
  EXPECT_TRUE(IsPrime(5));
  EXPECT_FALSE(IsPrime(6));
  EXPECT_TRUE(IsPrime(23));
}
}  // namespace

TEST是gtest的测试宏,我们的测试用例必须按照这样格式写,isPrimeTest是测试套的名字,一个测试套下可以有多个测试用例,那么Positive、Trivial就是我们测试用例的名称,EXPECT_EQ、EXPECT_FALSE和EXPECT_TRUE等等,都是gtest提供的测试断言,比如 EXPECT_EQ(1, Factorial(1));就是表示Factorial(1)和1是不是相等的,如果是则表示EXPECT_EQ会返回成功,否则失败,也即我们测试用例会失败或者成功。

  • 第三步,实现测试的main函数,当然我们也可以不用写main函数,那就需要连接gtest_main.a这个库。比如这样子编译:
g++ sample1.cc sample1_unittest.cc -lgtest -std=c++11 -lgtest_main -lpthread -o test1

然后运行测试程序test:

$ ./test

会有以下输出: 测试结果 或者是自己写一个main函数,函数定义如下:

#include <gtest/gtest.h>
int main(int argc, char** argv){
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

然后编译成我们的测试程序就OK了,也许很多同学会很惊讶为什么RUN_ALL_TESTS函数后会,我们的测试程序就能自动运行了呢? 这是一个题外话,当然我觉得这也是gtest的一大亮点。有兴趣不?

gtest的其它特性

4.1.事件

有时候在测试的时候,我们会在测试前做一些初始化活动,和测试后做一些清理工作,gtest提供了多种事件机制,非常方便我们在案例之前或之后做一些操作。总结一下gtest的事件一共有3种:
  1. 全局的,所有案例执行前后。
  2. TestSuite级别的,在某一批案例中第一个案例前,最后一个案例执行后。
  3. TestCase级别的,每个TestCase前后。
  • TestCase级别的
    也就是我们需要继承于testing::Test类,然后实现它的SetUp和TearDown方法,比如:
class QueueTestSmpl3 : public testing::Test {
 protected:

  virtual void SetUp() {
      //做一些准备工作
  }

  virtual void TearDown() {
     // 做一些清理工作
  }

OK,我们来一个案例吧,比如:

#ifndef GTEST_SAMPLES_SAMPLE3_INL_H_
#define GTEST_SAMPLES_SAMPLE3_INL_H_

#include <stddef.h>

template <typename E>  // E is the element type
class Queue;

template <typename E>  // E is the element type
class QueueNode {
 friend class Queue<E>;

public:
 // Gets the element in this node.
 const E& element() const { return element_; }

 // Gets the next node in the queue.
 QueueNode* next() { return next_; }
 const QueueNode* next() const { return next_; }

private:
 // Creates a node with a given element value.  The next pointer is
 // set to NULL.
 explicit QueueNode(const E& an_element)
     : element_(an_element), next_(nullptr) {}

 // We disable the default assignment operator and copy c'tor.
 const QueueNode& operator = (const QueueNode&);
 QueueNode(const QueueNode&);

 E element_;
 QueueNode* next_;
};

template <typename E>  // E is the element type.
class Queue {
public:
 // Creates an empty queue.
 Queue() : head_(nullptr), last_(nullptr), size_(0) {}

 // D'tor.  Clears the queue.
 ~Queue() { Clear(); }

 // Clears the queue.
 void Clear() {
   if (size_ > 0) {
     // 1. Deletes every node.
     QueueNode<E>* node = head_;
     QueueNode<E>* next = node->next();
     for (; ;) {
       delete node;
       node = next;
       if (node == nullptr) break;
       next = node->next();
     }

     // 2. Resets the member variables.
     head_ = last_ = nullptr;
     size_ = 0;
   }
 }

 size_t Size() const { return size_; }

 QueueNode<E>* Head() { return head_; }
 const QueueNode<E>* Head() const { return head_; }

 QueueNode<E>* Last() { return last_; }
 const QueueNode<E>* Last() const { return last_; }

 void Enqueue(const E& element) {
   QueueNode<E>* new_node = new QueueNode<E>(element);

   if (size_ == 0) {
     head_ = last_ = new_node;
     size_ = 1;
   } else {
     last_->next_ = new_node;
     last_ = new_node;
     size_++;
   }
 }

 E* Dequeue() {
   if (size_ == 0) {
     return nullptr;
   }

   const QueueNode<E>* const old_head = head_;
   head_ = head_->next_;
   size_--;
   if (size_ == 0) {
     last_ = nullptr;
   }

   E* element = new E(old_head->element());
   delete old_head;

   return element;
 }

 template <typename F>
 Queue* Map(F function) const {
   Queue* new_queue = new Queue();
   for (const QueueNode<E>* node = head_; node != nullptr;
        node = node->next_) {
     new_queue->Enqueue(function(node->element()));
   }

   return new_queue;
 }

private:
 QueueNode<E>* head_;  // The first node of the queue.
 QueueNode<E>* last_;  // The last node of the queue.
 size_t size_;  // The number of elements in the queue.

 // We disallow copying a queue.
 Queue(const Queue&);
 const Queue& operator = (const Queue&);
};

#endif  // GTEST_SAMPLES_SAMPLE3_INL_H_

上面是一个队列的实现,那么我们怎么做测试呢?比如如果我们测试它的入队和出队方法,就可以在SetUp方法入队一些数据,然后测试能否出队一些正确的数据。

#include "sample3-inl.h"
#include "gtest/gtest.h"
namespace {
class QueueTestSmpl3 : public testing::Test {
 protected: 

  virtual void SetUp() {
    q0_.Enqueue(1);
    q1_.Enqueue(2);
    q2_.Enqueue(3);
  }

   virtual void TearDown() {
   }

  static int Double(int n) {
    return 2*n;
  }

  // A helper function for testing Queue::Map().
  void MapTester(const Queue<int> * q) {
    // Creates a new queue, where each element is twice as big as the
    // corresponding one in q.
    const Queue<int> * const new_q = q->Map(Double);

    // Verifies that the new queue has the same size as q.
    ASSERT_EQ(q->Size(), new_q->Size());

    // Verifies the relationship between the elements of the two queues.
    for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();
         n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {
      EXPECT_EQ(2 * n1->element(), n2->element());
    }

    delete new_q;
  }

  // Declares the variables your tests want to use.
  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

// Tests Dequeue().
TEST_F(QueueTestSmpl3, Dequeue) {
  int * n = q0_.Dequeue();
  EXPECT_TRUE(n == nullptr);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0u, q1_.Size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1u, q2_.Size());
  delete n;
}
}  // namespace

我们在SetUp里压入了数据,然后我们测试是否能够出队期望的数据。那么这个SetUp有一点好的是,每一个测试用例运行前都会调用,每一个测试用例运行后都会调用TearDown方法。

哦,有了这个简单的测试用例的事件,我们来看看如何定义个测试套的事件。

  • 测试套事件

这个相对来说比较简单,相对上面的实现就是替换成静态的SetUpTestCase,比如上代码吧:

class FooTest : public testing::Test {
 protected:
 //准备资源
  static void SetUpTestCase() {
    shared_resource_ = new ;
  }
  //释放资源
  static void TearDownTestCase() {
    delete shared_resource_;
    shared_resource_ = NULL;
  }
  // 资源
  static T* shared_resource_;
};
  • 全局事件

也是很简单的,就是继承于testing::Environment而已。

class FooEnvironment: public testing::Environment
{
    public:
        virtual void SetUp()
        {
            printf("Environment SetUp!\n");
            a = 100;
        }
        virtual void TearDown()
        {
            printf("Environment TearDown!\n");
        }
        int a;     //共享数据
};
FooEnvironment* foo_env;  //对象指针声明
TEST(firstTest, first)    //访问共享数据并改变它的值
{
    printf("in the firstTest, foo_env->p is %d\n", foo_env->a);
    foo_env->a ++;
}
TEST(secondTest, second)  //访问共享数据
{
    printf("in the secondTest, foo_env->p is %d\n", foo_env->a);
}
int main(int argc, char* argv[])
{
    foo_env = new FooEnvironment;
    testing::AddGlobalTestEnvironment(foo_env);     //注册
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

4.2.参数化

**问题的由来,比如我们第一个测试示例中有IsPrime函数,判断是否为素数** 那么我们的测试用例可能会是这样:
TEST(IsPrimeTest, HandleTrueReturn)
{
    EXPECT_TRUE(IsPrime(3));
    EXPECT_TRUE(IsPrime(5));
    EXPECT_TRUE(IsPrime(11));
    EXPECT_TRUE(IsPrime(23));
    EXPECT_TRUE(IsPrime(17));
}

那这样一个测试有什么问题呢?这里我们输入了5个数,如果要测试100,10000个数怎么办呢?难道写一万遍EXPECT_TRUE语句?好的,那既然不能如此,新的方案呢?就是使用参数化测试方法。How to do?

  • 第一步: 告诉gtest你的参数类型是什么 你必须添加一个类,继承testing::TestWithParam,其中T就是你需要参数化的参数类型,比如上面的例子,我需要参数化一个int型的参数
class IsPrimeParamTest : public::testing::TestWithParam<int>
{

};
  • 第二步:告诉gtest你拿到参数的值后,具体做些什么样的测试

这里,我们要使用一个新的宏(嗯,挺兴奋的):TEST_P,在TEST_P宏里,使用GetParam()获取当前的参数的具体值,并且告诉gtest我们要做EXPECT_TRUE测试。

TEST_P(IsPrimeParamTest, HandleTrueReturn)
{
    int n =  GetParam();
    EXPECT_TRUE(IsPrime(n));
}
  • 第三步:如何告诉gtest你的测试参数
INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(3, 5, 11, 23, 17));

第一个参数是测试案例的前缀,可以任意取。 第二个参数是测试案例的名称,需要和之前定义的参数化的类的名称相同,如:IsPrimeParamTest 第三个参数是可以理解为参数生成器,上面的例子使用test::Values表示使用括号内的参数。Google提供了一系列的参数生成的函数:

*  Range(begin, end[, step])	:范围在begin~end之间,步长为step,不包括end
*  Values(v1, v2, ..., vN)	  : v1,v2到vN的值
*  ValuesIn(container) and ValuesIn(begin, end)	:  从一个C类型的数组或是STL容器,或是迭代器中取值
*  Bool()	: 取false 和 true 两个值
*  Combine(g1, g2, ..., gN)	:这个比较强悍,它将g1,g2,...gN进行排列组合,g1,g2,...gN本身是一个参数生成器,每次分别从g1,g2,..gN中各取出一个值,组合成一个元组(Tuple)作为一个参数。说明:这个功能只在提供了<tr1/tuple>头的系统中有效。gtest会自动去判断是否支持tr/tuple,如果你的系统确实支持,而gtest判断错误的话,你可以重新定义宏GTEST_HAS_TR1_TUPLE=1。

4.3.死亡测试

死亡测试”名字比较恐怖,这里的“死亡”指的的是程序的崩溃。通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的“死亡测试”。gtest的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证。这里就不详述了,有用到的时候,我们再单独讨论,当然大家任何时间都可以找Lee哥。

4.4.深入解析gtest框架

我们来回忆下测试用例的写法:
#include <gtest/gtest.h>
int main(int argc, char** argv){
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

// Tests factorial of negative numbers.  
TEST(FactorialTest, Factorial_parameter_is_negative_num) {  
  EXPECT_EQ(1, Factorial(-5));  
  EXPECT_EQ(1, Factorial(-1));  
  EXPECT_GT(Factorial(-10), 0);  
}

在main函数里,我们根本就没有写调用测试用例的代码,那测试用例都是怎么跑起来的来?这里我们就要分析分析TEST这个东西,其实我们去翻看gtest的代码,会发现TEST是一个宏定义,其定义如下:

#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif

#define GTEST_TEST(test_case_name, test_name)\
  GTEST_TEST_(test_case_name, test_name, \
              ::testing::Test, ::testing::internal::GetTestTypeId())
			  
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
 public:\
  GTEST_TEST_CLASS_NAME_(test_case_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_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ =\
    ::testing::internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, NULL, NULL, \
        ::testing::internal::CodeLocation(__FILE__, __LINE__), \
        (parent_id), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        new ::testing::internal::TestFactoryImpl<\
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()

看了上面代码是不是觉得很晕了,不要晕啊,这是宏非常美妙的用法,唯一要提的就是#test_case_name和#test_name,这个“#”就是C语言里一个特定的宏,就是转换成字符串。那OK,这里的MakeAndRegisterTestInfo是什么东西:

TestInfo* MakeAndRegisterTestInfo(
    const char* test_case_name,
    const char* name,
    const char* type_param,
    const char* value_param,
    CodeLocation code_location,
    TypeId fixture_class_id,
    SetUpTestCaseFunc set_up_tc,
    TearDownTestCaseFunc tear_down_tc,
    TestFactoryBase* factory) {
  TestInfo* const test_info =
      new TestInfo(test_case_name, name, type_param, value_param,
                   code_location, fixture_class_id, factory);
  GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
  return test_info;
  
 inline UnitTestImpl* GetUnitTestImpl() {
      return UnitTest::GetInstance()->impl();
  }
}

也就是把测试用例添加到UnitTest::GetInstance()->impl()里了,AddTestInfo的实现如下:

void TestCase::AddTestInfo(TestInfo * test_info) {
  test_info_list_.push_back(test_info);
  test_indices_.push_back(static_cast<int>(test_indices_.size()));
}

那我们再来看看执行测试用例时调用的RUN_ALL_TESTS的实现吧。

inline int RUN_ALL_TESTS() {
  return ::testing::UnitTest::GetInstance()->Run();
}

我们又一次看到了UnitTest::GetInstance(),然后调用run方法:

// Runs every test in this TestCase.
void TestCase::Run() {
  if (!should_run_) return;

  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
  impl->set_current_test_case(this);

  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();

  repeater->OnTestCaseStart(*this);
  impl->os_stack_trace_getter()->UponLeavingGTest();
  internal::HandleExceptionsInMethodIfSupported(
      this, &TestCase::RunSetUpTestCase, "SetUpTestCase()");

  const internal::TimeInMillis start = internal::GetTimeInMillis();
  for (int i = 0; i < total_test_count(); i++) {
    GetMutableTestInfo(i)->Run();
  }
  elapsed_time_ = internal::GetTimeInMillis() - start;

  impl->os_stack_trace_getter()->UponLeavingGTest();
  internal::HandleExceptionsInMethodIfSupported(
      this, &TestCase::RunTearDownTestCase, "TearDownTestCase()");

  repeater->OnTestCaseEnd(*this);
  impl->set_current_test_case(NULL);
}