[C++]初识google test--单元测试神器

初识google test

gtest是google的一个开源项目,专门用来做单元测试的。学习难度不算非常的大,适用于多个平台。主要就是使用断言来判断代码的正确性。

google test这个github中可以下载gtest的源码,并且在文件中有make文件夹,可以用terminal,make产生可执行文件,实际上在makefile里面写出了编译成可执行文件所需要的代码。可以打开这个文件改变其中的文件名参数,来编译不同的代码。阅读sample,也可以对gtest有大概的了解。

以下是我简单翻译的github上的知识介绍。

为什么选择gtest?

    1. 测试应该是独立的和可重复的。gtest通过以不同的对像来运行他们,从而分割不同的测试。
    1. 测试应该被很好的组织和反映测试代码的结构。
    1. 测试应该是可重用的和可移用的,能够用于不同的平台(操作系统)。
    1. 当测试失败时,提供足够多的信息。
    1. gtest可以使作者专心于测试的内容而不用在意其他的东西。
    1. 测试应该是迅速的。

基本概念

gtest通过断言(assertions)去测试代码的行为。一个test case可以有多个test,所以应该根据测试的框架去编写test。当多个tests在一个test case需要公用对象和子程序时,应该把他们放在test fixture 类中。

Assertion(断言)

gtest是通过断言来判定代码的行为的,如果断言失败了,gtest会输出断言的源文件和所在的行号。gtest提供两种版本的断言。

ASSERT_* 产生致命错误,直接导致函数终止。

EXPECT_* 产生非致命错误,函数继续运行。

因为ASSERT_失败时会直接导致程序终止,所以有可能直接掉过 clean-up的代码,从而导致内存泄露,所以需要格外的注意。

可以使用<<定值错误信息:

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

任何可以被ostream接受的类型,都可以被定值为错误信息。

基本断言

这些断言是用来剪短的判断正误的。

Fatal assertionNonfatal assertionVerifies
ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition is true
ASSERT_FALSE(condition);EXPECT_FALSE(condition);condition is false

适用于:Linux, Windows, Mac.

二元比较

这些断言适用于比较两个数的。

Fatal assertionNonfatal assertionVerifies
ASSERT_EQ(val1,val2);EXPECT_EQ(val1,val2);val1 == val2
ASSERT_NE(val1,val2);EXPECT_NE(val1,val2);val1 != val2
ASSERT_LT(val1,val2);EXPECT_LT(val1,val2);val1 < val2
ASSERT_LE(val1,val2);EXPECT_LE(val1,val2);val1 <= val2
ASSERT_GT(val1,val2);EXPECT_GT(val1,val2);val1 > val2
ASSERT_GE(val1,val2);EXPECT_GE(val1,val2);val1 >= val2

如果断言错误,则gtest会输出两个val。

两个参数一定都是可比较的,否则会出现编译错误。

值得注意的是,如果比较的是两个const char*,不要使用ASSERT_EQ(),因为他们会比较两个指针指向的地址是否是一样的,而不是比较他们的值。所以应该使用ASSERT_STREQ(),后面会提到。特别是,如果要比较null,应该这么使用ASSERT_STREQ(NULL, c_string)(因为NULL在宏定义中认为是0,在c++11中给出nullptr,可以解决这个问题。)。然而如果是string,则应该使用ASSERT_EQ()。

字符串比较

这里指的string其实是c string(char *)。

Fatal assertionNonfatal assertionVerifies
ASSERT_STREQ(str1,str2);EXPECT_STREQ(str1,_str_2);the two C strings have the same content
ASSERT_STRNE(str1,str2);EXPECT_STRNE(str1,str2);the two C strings have different content
ASSERT_STRCASEEQ(str1,str2);EXPECT_STRCASEEQ(str1,str2);the two C strings have the same content, ignoring case
ASSERT_STRCASENE(str1,str2);EXPECT_STRCASENE(str1,str2);the two C strings have different content, ignoring case

注意到‘CASE’断言中意味着忽略case。

实际上他们都接受wchar_t。如果wchar_t比较失败了,他们的值会被输出为UTF-8的原始字符串。

一个NULL指针和空字符串是不一样的!

简单例子

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

TEST接受两个参数,第一个是case的名字,第二个是test的名字。注意他们都必须是C++的标识符,并且不能含有下划线。一个test的完整名字包括他的case和自己的名字。

我们举个简单的例子:

int Factorial(int n); // Returns the factorial of n

它的测试代码可能像这样:

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(1, Factorial(0));
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

这个测试中有一个case FactorialTest, 以及两个test,HandlesZeroInputHandlesPositiveInput。他们测试输出时会一起给出信息。从而体现的测试代码的结构。

Test Fixture

如果你发现写出两个或多个测试他们都会操作于相似的数据时,你可以使用test fixture。

基本步骤为:

    1. 继承于::testing::Test。用protected: 或则 public:开始,由此他们才可以访问fixture的对象。
    1. 声明变量。
    1. 如果需要的话,写出SetUp()作为构造函数,TearDown()作为析构函数。
    1. 用TEST_F()来替换TEST()。
#ifndef GTEST_SAMPLES_SAMPLE3_INL_H_
#define GTEST_SAMPLES_SAMPLE3_INL_H_

#include <stddef.h>


// Queue is a simple queue implemented as a singled-linked list.
//
// The element type must support copy constructor.
template <typename E>  // E is the element type
class Queue;

// QueueNode is a node in a Queue, which consists of an element of
// type E and a pointer to the next node.
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_(NULL) {}

  // 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_(NULL), last_(NULL), 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 == NULL) break;
        next = node->next();
      }

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

  // Gets the number of elements.
  size_t Size() const { return size_; }

  // Gets the first element of the queue, or NULL if the queue is empty.
  QueueNode<E>* Head() { return head_; }
  const QueueNode<E>* Head() const { return head_; }

  // Gets the last element of the queue, or NULL if the queue is empty.
  QueueNode<E>* Last() { return last_; }
  const QueueNode<E>* Last() const { return last_; }

  // Adds an element to the end of the queue.  A copy of the element is
  // created using the copy constructor, and then stored in the queue.
  // Changes made to the element in the queue doesn't affect the source
  // object, and vice versa.
  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_++;
    }
  }

  // Removes the head of the queue and returns it.  Returns NULL if
  // the queue is empty.
  E* Dequeue() {
    if (size_ == 0) {
      return NULL;
    }

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

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

    return element;
  }

  // Applies a function/functor on each element of the queue, and
  // returns the result in a new queue.  The original queue is not
  // affected.
  template <typename F>
  Queue* Map(F function) const {
    Queue* new_queue = new Queue();
    for (const QueueNode<E>* node = head_; node != NULL; 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_
#include "sample3.h"
#include "gtest/gtest.h"

// To use a test fixture, derive a class from testing::Test.
class QueueTest : public testing::Test {
 protected:  // You should make the members protected s.t. they can be
             // accessed from sub-classes.

  // virtual void SetUp() will be called before each test is run.  You
  // should define it if you need to initialize the varaibles.
  // Otherwise, this can be skipped.
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() will be called after each test is run.
  // You should define it if there is cleanup work to do.  Otherwise,
  // you don't have to provide it.
  //
  // virtual void TearDown() {
  // }

  // A helper function that some test uses.
  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 != NULL; 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_;
};

// When you have a test fixture, you define a test using TEST_F
// instead of TEST.

// Tests the default c'tor.
TEST_F(QueueTest, DefaultConstructor) {
  // You can access data in the test fixture here.
  EXPECT_EQ(0u, q0_.Size());
}

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

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

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

// Tests the Queue::Map() function.
TEST_F(QueueTest, Map) {
  MapTester(&q0_);
  MapTester(&q1_);
  MapTester(&q2_);
}

主函数写法

在主函数中使用RUN_ALL_TESTS()。

#include "this/package/foo.h"
#include "gtest/gtest.h"

namespace {

// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if its body
  // is empty.

  FooTest() {
    // You can do set-up work for each test here.
  }

  virtual ~FooTest() {
    // You can do clean-up work that doesn't throw exceptions here.
  }

  // If the constructor and destructor are not enough for setting up
  // and cleaning up each test, you can define the following methods:

  virtual void SetUp() {
    // Code here will be called immediately after the constructor (right
    // before each test).
  }

  virtual void TearDown() {
    // Code here will be called immediately after each test (right
    // before the destructor).
  }

  // Objects declared here can be used by all tests in the test case for Foo.
};

// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
  const string input_filepath = "this/package/testdata/myinputfile.dat";
  const string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(0, f.Bar(input_filepath, output_filepath));
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  // Exercises the Xyz feature of Foo.
}

}  // namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值