当前位置:   article > 正文

GTest基础学习-04-第3个单元测试-测试夹具test fixture

test fixture

这篇来学习一下Gtest中更高级一些的特性test fixture,测试夹具的基本上使用。什么的场景需要使用到测试夹具呢?测试夹具是哪个宏,这篇来学习这个主题。

 

1.什么叫test fixture

什么是测试夹具,这个概念在任何xUnit系列的单元测试框架都会出现。一般是指,所有的测试用例都可以共享的步骤,例如初始化和事后清理操作,能提供这个功能的对象叫test fixture。

在test fixture是这样使用的,我们需要单独写一个类并且继承testing::Test,如果有必要都需要实现SetUp()和TearDown()函数,当然这两个至少有一个需要实现,不然我们不会去使用test fixture这个功能。在SetUp()函数中一般写一些初始化操作,例如测试对象的创建,对象的成员变量的初始化等,在TearDown()一般用来集中清除资源操作,例如销毁在SetUp()中创建的被测试对象。

 

2.测试夹具使用

sample03.h代码

  1. #ifndef GTEST_SAMPLES_SAMPLE03_INL_H_
  2. #define GTEST_SAMPLES_SAMPLE03_INL_H_
  3. #include <stddef.h>
  4. // Queue类是一个简单的队列,内部是基于单链表实现
  5. //
  6. // 元素数据类型必须支持拷贝构造
  7. template <typename E> // E 是元素的类型。使用了模板类
  8. class Queue;
  9. // QueueNode 是Queue对象中的一个结点, 存储元素E和指向下一个结点的指针
  10. template <typename E> // E 是元素的类型。使用了模板类
  11. class QueueNode {
  12. friend class Queue<E>;
  13. public:
  14. // 获取结点中的元素
  15. const E& element() const { return element_; }
  16. // 获取下一个结点
  17. QueueNode* next() { return next_; }
  18. const QueueNode* next() const { return next_; }
  19. private:
  20. // 创建一个结点,元素是参数element,下一个结点的指针设置为NULL
  21. explicit QueueNode(const E& an_element)
  22. : element_(an_element), next_(nullptr) {}
  23. // 这里禁用默认的赋值操作和构造
  24. const QueueNode& operator = (const QueueNode&);
  25. QueueNode(const QueueNode&);
  26. E element_;
  27. QueueNode* next_;
  28. };
  29. template <typename E> // E 是可以任意类型
  30. class Queue {
  31. public:
  32. // 创建一个空的队列
  33. Queue() : head_(nullptr), last_(nullptr), size_(0) {}
  34. // 析构函数 清空队列
  35. ~Queue() { Clear(); }
  36. // 清空队列函数
  37. void Clear() {
  38. if (size_ > 0) {
  39. // 1. 删除每一个结点
  40. QueueNode<E>* node = head_;
  41. QueueNode<E>* next = node->next();
  42. for (; ;) {
  43. delete node;
  44. node = next;
  45. if (node == nullptr) break; //跳槽循环在这行
  46. next = node->next();
  47. }
  48. // 2.重置成员变量,队列大小为0,头结点和尾结点都设置为空指针
  49. head_ = last_ = nullptr;
  50. size_ = 0;
  51. }
  52. }
  53. // 获取队列元素个数
  54. size_t Size() const { return size_; }
  55. // 获取队列头部元素, 如果队列为空返回NULL
  56. QueueNode<E>* Head() { return head_; }
  57. const QueueNode<E>* Head() const { return head_; }
  58. // 获取队列尾部元素, 如果队列为空返回NULL
  59. QueueNode<E>* Last() { return last_; }
  60. const QueueNode<E>* Last() const { return last_; }
  61. // 往队列尾部插入一个元素,使用拷贝构造创建这个元素并存储在队列中
  62. //对队列中的元素所做的更改不会影响源对象,反之亦然。
  63. void Enqueue(const E& element) {
  64. QueueNode<E>* new_node = new QueueNode<E>(element);
  65. if (size_ == 0) {
  66. head_ = last_ = new_node;
  67. size_ = 1;
  68. }
  69. else {
  70. last_->next_ = new_node;
  71. last_ = new_node;
  72. size_++;
  73. }
  74. }
  75. // 删除头部元素并返回这个元素,如果队列为空,返回NULL
  76. E* Dequeue() {
  77. if (size_ == 0) {
  78. return nullptr;
  79. }
  80. const QueueNode<E>* const old_head = head_;
  81. head_ = head_->next_;
  82. size_--;
  83. if (size_ == 0) {
  84. last_ = nullptr;
  85. }
  86. E* element = new E(old_head->element());
  87. delete old_head;
  88. return element;
  89. }
  90. // 提供一个函数,遍历队列中每一元素,调用这个函数,返回结果存储在一个新的队列,源队列对象不受影响
  91. template <typename F>
  92. Queue* Map(F function) const {
  93. Queue* new_queue = new Queue();
  94. for (const QueueNode<E>* node = head_; node != nullptr;
  95. node = node->next_) {
  96. new_queue->Enqueue(function(node->element()));
  97. }
  98. return new_queue;
  99. }
  100. private:
  101. QueueNode<E>* head_; // 队列头结点
  102. QueueNode<E>* last_; // 队列尾结点
  103. size_t size_; // 队列元素个数
  104. // 这里不允许复制队列.
  105. Queue(const Queue&);
  106. const Queue& operator = (const Queue&);
  107. };
  108. #endif // GTEST_SAMPLES_SAMPLE03_INL_H_

 

TestSample03.cpp代码

  1. //在这个例子中,我们使用Gtest中更高级一点的特性,叫test fixture, 一般翻译测试夹具
  2. //
  3. // 测试夹具是用来放置对象和函数,并共享给所有的测试用例在一个测试cpp文件中
  4. // 使用测试夹具能够避免测试代码重复,特别是哪些每个测试都需要用到的初始化和清除操作
  5. //
  6. //测试从代码共享的意义上共享的是测试夹具,而不是数据共享。
  7. //每个测试都有自己的最新副本夹具。 您不能期望一次测试修改的数据是传递给另一个测试,这是一个坏主意.
  8. //
  9. // 这么设计的原因是保持测试的独立性和可重复性
  10. // 一个测试不能受其他的测试失败而导致失败
  11. //如果一个测试依赖于另一个测试,这两个测试应该是被看作一个大的测试
  12. //
  13. // 响应测试成功或失败的宏(例如 EXPECT_TRUE, FAIL, 等)需要知道当前的测试是什么
  14. // 当Google test打印测试结果,如果遇到失败,打印结果会告诉失败是哪一个测试
  15. //从技术上讲,这些宏会调用Test类的成员函数。 因此,您不能在全局函数中使用他们。
  16. //这就是为什么您应该放置测试子例程在测试夹具中。
  17. //
  18. #include "sample03.h"
  19. #include "gtest/gtest.h"
  20. namespace {
  21. // 想要使用测试夹具,需要定义一个类并继承testing::Test
  22. class QueueTestSmpl3 : public testing::Test {
  23. protected: // 这里使用保护关键字,成员都受访问保护,可以从子类中访问到父类受保护的成员
  24. // 每个TEST宏的测试在开始运行之前都会调用virtual void SetUp()
  25. //应该实现这个SetUp(),例如一些变量初始化,如果用不到就不需要提供SetUp()
  26. void SetUp() override {
  27. q1_.Enqueue(1);
  28. q2_.Enqueue(2);
  29. q2_.Enqueue(3);
  30. }
  31. // 每个TEST宏的测试在结束之前都会调用virtual void TearDown()
  32. // 你应该定义在TearDown中做哪些清除操作,否则不应该提供这个TearDown函数
  33. //
  34. // virtual void TearDown() {
  35. // }
  36. // 一个帮助函数,有些测试需要用到
  37. static int Double(int n) {
  38. return 2 * n;
  39. }
  40. // 一个帮助函数,为了测试 Queue::Map().
  41. void MapTester(const Queue<int> * q) {
  42. // 新建一个对象,队列中每一个元素都是q中元素的两倍大小
  43. const Queue<int> * const new_q = q->Map(Double);
  44. // 确认新的队列大小和q是一样大
  45. ASSERT_EQ(q->Size(), new_q->Size());
  46. // 确认两个队列之间元素的关系
  47. for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();
  48. n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {
  49. EXPECT_EQ(2 * n1->element(), n2->element());
  50. }
  51. delete new_q;
  52. }
  53. // 声明接下来你想用到的几个变量
  54. Queue<int> q0_;
  55. Queue<int> q1_;
  56. Queue<int> q2_;
  57. };
  58. // 当你有需要测试夹具( fixture翻译为夹具),需要使用 TEST_F宏编写测试用例,不用TEST宏
  59. // 测试默认构造函数
  60. TEST_F(QueueTestSmpl3, DefaultConstructor) {
  61. // 可以访问元素使用TEST_IF
  62. EXPECT_EQ(0u, q0_.Size());
  63. }
  64. // 测试 Dequeue().
  65. TEST_F(QueueTestSmpl3, Dequeue) {
  66. int * n = q0_.Dequeue();
  67. EXPECT_TRUE(n == nullptr);
  68. n = q1_.Dequeue();
  69. ASSERT_TRUE(n != nullptr);
  70. EXPECT_EQ(1, *n);
  71. EXPECT_EQ(0u, q1_.Size());
  72. delete n;
  73. n = q2_.Dequeue();
  74. ASSERT_TRUE(n != nullptr);
  75. EXPECT_EQ(2, *n);
  76. EXPECT_EQ(1u, q2_.Size());
  77. delete n;
  78. }
  79. // 测试函数Queue::Map()
  80. TEST_F(QueueTestSmpl3, Map) {
  81. MapTester(&q0_);
  82. MapTester(&q1_);
  83. MapTester(&q2_);
  84. }
  85. } // namespace

在拷贝我的代码过程需要注意,我在每个cpp文件都右键-属性-预编译头中选择了不使用预编译头。

点击生成解决方案,查看gtest的运行输出结果

红圈这几个测试结果是本篇关于test fixture的用例运行结果,注意看倒数第3行,Global test environment tear-down,这个gtest在运行全部测试用例的时候,内部也使用了TearDown()这个机制。

 

3.总结

这篇重点是介绍学习test fixture的基本使用。被测试的对象是一个自定义的队列数据结构的MyQueue类。测试代码中用到了new开辟内存空间和delete删除内存空间,还有模板类的定义。

1.使用TEST_F(测试夹具名称,测试用例名称)

这个测试夹具名称一般就是我们在cpp文件中提前写的自定义类的名称,这个类是必须继承testing::Test

 

本篇就新使用到了几个测试断言宏

  1. ASSERT_EQ //强断言 两个对象是否相等
  2. ASSERT_TRUE //强断言,表达式或函数返回值是不是true

这个看起来和前面的EXPECT_EQ和EXPECT_TRUE是一样的作用,很自然,我们就会去想ASSER和EXPECT有什么区别。

 

4.ASSERT和EXPECT的区别

我们在gtest中光标定位在ASSERT_TURE,按下键盘F12,可以看到定义这个方法的代码如下

        通过对比ASSERT_TRUE和EXPECT_TRUE中调用代码的实现,发现两个断言宏的根本区别是在于NONFATAL 和FATAL的区别。上面可以看到EXPECT是调用NONFATAL,而ASSERT是调用FATAL实现。在不继续看代码之前,我们先搞清楚FATAL这个英文单词的意思,翻译过来就是致命。也就是ASSERT走的是致命失败,而EXPECT走的是非致命失败。

        接着分别来看GTEST_NONFATAL_FAILURE_GTEST_FATAL_FAILURE_的内部实现

上面我们点击右侧TestPartResult这个接口,点击F12。

马上就接近真相,通过红圈两个枚举的注释我们得到了答案。

ASSERT_TRUE :如果失败,测试应该被终止

EXPECT_TRUE:如果失败,测试可以继续往下运行

根据这个结论,但是我们当前的水平不好写代码去验证,因为gtest的运行报告不会显示我们自己写的std::cout语句的打印数据。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/672664
推荐阅读
相关标签
  

闽ICP备14008679号