【C++ UnitTest】基于Google Test 编写 test fixture

什么是test fixture

测试固件(test fixture)是一种用于测试的环境设置,它用于确保在测试运行期间有一致的测试环境。测试固件允许你在多个测试用例之间共享初始化和清理代码,并且可以确保每个测试用例在相同的环境下执行,从而提高了测试的可靠性和一致性。

怎么使用test fixture

当业务代码需要你实现一个新的Class,不管是什么类,必然有相应的函数和初始化参数,对于类中定义的函数实现基本基于同一套初始化参数或者不是基于某些参数,但始终都是在同一个类中的实现。对于这个类的测试需求,就可以使用test fixture。

以官方文档中的代码实例,代码中实现了队列的创建、添加、删除、节点访问等功能。

#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_(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;
        }
    }

    // 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 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;
    }

    // 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 != 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&);
};

由于使用的是test fixture,这里使用的是TEST_F而不是TEST
测试的需求包括对QueueNode类默认初始化参数的测试项 DefaultConstructor,QueueNode类中Dequeue成员函数的功能测试项Dequeue,QueueNode类中Map成员函数的功能测试项Map。当然你也可以添加对Enqueue函数的测试项,只要可以符合你的预期,无论是预期对还是预期错。

包含测试夹具的UT代码:

#include "gtest/gtest.h"
namespace {
    // To use a test fixture, derive a class from testing::Test.
    class QueueTestSmpl3 : 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 variables.
// Otherwise, this can be skipped.
        void SetUp() override {
            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 != 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_;
    };

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

    // Tests the default c'tor.
    TEST_F(QueueTestSmpl3, DefaultConstructor) {
        // You can access data in the test fixture here.
        EXPECT_EQ(0u, q0_.Size());//默认的q0_并没有赋值
    }

    // Tests Dequeue().
    TEST_F(QueueTestSmpl3, Dequeue) {
        int* n = q0_.Dequeue();
        EXPECT_TRUE(n == nullptr);//当q0_的size=0时,返回空指针

        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;
    }

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

测试执行结果

这里一共测试了三个分项,都符合预期结果。

【图片】

如果我这里修改其中一条测试的预期,故意让测试不通过,会怎样?

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

        n = q1_.Dequeue();//这里执行完Dequeue,返回的参数不应该为nullptr
        ASSERT_TRUE(n == nullptr);//ASSERT_TRUE(n != nullptr);

执行结果中可以看到原本全都绿色代表PASS的结果,出现了红色的FAILED信息,并且是在[ RUN ] QueueTestSmpl3.Dequeue后出现的FAILED。由于我是提前清楚自己修改的UT是在哪一行,从执行结果上也基本能锁定出错的位置和信息。

\test.cpp(66): error: Value of: n == nullptr
  Actual: false
Expected: true

在这里插入图片描述
不同的预期(EXCEPT_)有不同的结果,无论你是判断“对”还是判断“错”,取决编写UT的人怎么去定义它。

SetUp()和TearDown()

SetUp()TearDown() 是用于测试固件(test fixture)的特殊方法,它们分别用于在每个测试用例运行前和运行后执行一些初始化和清理工作。

    class QueueTestSmpl3 : 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 variables.
// Otherwise, this can be skipped.
        void SetUp() override {
            q1_.Enqueue(1);
            q2_.Enqueue(2);
            q2_.Enqueue(3);
        }
SetUp():

SetUp() 方法在每个测试用例运行之前被调用。它通常用于设置测试所需的环境、初始化对象、加载数据等操作。通过在 SetUp() 中进行这些初始化操作,你可以确保每个测试用例在相同的测试环境下执行,提高了测试的一致性和可靠性。
这里的SetUp() 里分别对q1_,q2_变量进行了赋值,以便后续的测试工作。

TearDown():

TearDown() 方法在每个测试用例运行之后被调用。它通常用于清理测试过程中所产生的临时资源、释放内存、关闭文件或者数据库连接等操作。通过在 TearDown() 中进行这些清理工作,你可以确保在测试结束后恢复到一个干净的状态,避免了测试中可能产生的副作用。
这里的TearDown()并没有对原TearDown()函数进行override。

SetUpTestCase()和TearDownTestCase()

此外,还有本段代码中没有涉及的特殊方法,SetUpTestCase() 和 TearDownTestCase() 是用于在unittest中设置测试固件的特殊方法,它们用于在测试用例集合(test case class)的开始和结束时执行一些初始化和清理工作。

SetUpTestCase():

SetUpTestCase() 方法在测试用例集合(test case class)中的第一个测试用例运行之前被调用,通常用于执行一些针对整个测试用例集合的初始化操作。比如,可能需要在这里建立测试用例之间共享的环境、加载测试数据等操作。

TearDownTestCase():

TearDownTestCase() 方法在测试用例集合中的最后一个测试用例运行之后被调用,通常用于执行一些针对整个测试用例集合的清理工作。比如,可能需要在这里清理测试过程中产生的临时文件、释放资源等操作。

这两个方法的使用场景与 SetUp()TearDown() 类似,不同之处在于它们是针对整个测试用例集合的,而不是针对单个测试用例。通过在测试用例集合的开始和结束时执行一些初始化和清理工作,你可以确保测试环境的一致性,并在测试结束后保持环境的干净和稳定。

要将Python的unittest测试框架转换为Google Test,您需要进行一些手动的更改和调整。下面是一些步骤来帮助您进行转换: 1. 导入Google Test库:首先,确保您已经安装了Google Test库并将其添加到项目中。您可以从Google Test的官方仓库(https://github.com/google/googletest)下载并安装。 2. 调整测试用例的结构:Google Test采用了不同的测试用例结构。在unittest中,测试用例是继承自unittest.TestCase的类,而在Google Test中,测试用例是通过TEST宏定义的函数。因此,您需要将每个unittest的测试用例转换为一个独立的TEST函数。 3. 调整断言语句:Google Test使用不同的断言宏来进行断言判断。在unittest中常用的断言方法如assertEqual、assertTrue等,对应的Google Test断言宏是EXPECT_EQ、EXPECT_TRUE等。您需要将unittest中的断言语句逐个替换为对应的Google Test断言宏。 4. 设置测试夹具(Fixture):Google Test提供了夹具(Fixture)功能,用于在测试用例执行之前和之后设置/清理环境。如果您的unittest中使用了setUp和tearDown方法,您需要将它们转换为Google Test夹具功能。可以使用TEST_F宏定义来创建带有夹具的测试用例。 5. 运行测试:完成转换后,您可以使用Google Test提供的测试运行器来运行测试。您可以选择使用命令行工具或集成开发环境(IDE)中的插件来运行Google Test。 需要注意的是,Google Testunittest之间还有其他区别,例如测试报告的输出格式和一些高级功能的支持。因此,您可能需要进一步了解Google Test的文档和示例代码来进行更全面的转换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值