2024年2月19日

如何在运行时使用 CppUTest 注入模拟类

作者 柳 永强
内容目录

这是一个简单的指南,教您如何将模拟类注入产品代码中,并让您的产品类在 CppUTest 单元框架下可测试。

下图是类之间的关系。ProductClassB 依赖于 ProductClassA(ProductClassB 调用 ProductClassB.method() 中的 ProductClassA.method1()),现在当我们对 ProductClassB 进行单元测试时,应该模拟 ProductClassA。

下面的代码展示了如何使用 CppUTest 使用新方法来模拟依赖的类。

ProductClassA 有一些方法,如果您希望这些方法可以被模拟,则应将这些方法声明为虚拟方法。

ProductClassA.hpp:

#ifndef _INCLUDE_PRODUCT_A_CLASS_
#define _INCLUDE_PRODUCT_A_CLASS_
#include <string>

class ProductClassA {
public:
ProductClassA() {
}

~ProductClassA() {
}
public:
virtual int method1(const std::string& arg1);
virtual int method2(const std::string& arg1);
};

#endif

ProductClassA.cpp:

#include <iostream>
#include "ProductClassA.hpp"

int ProductClassA::method1(const std::string& arg1) {
std::cout << arg1 << " ProductClassA class method1" <<std::endl;
return 0;
}

int ProductClassA::method2(const std::string& arg1) {
std::cout << arg1 << " ProductClassA class method2" <<std::endl;
return 0;
}

如果你想在对 ProductClassB 进行单元测试时模拟依赖的类 ProductClassA,则应在 ProductClassB 中声明一个具有引用类型的私有 ProductClassA 变量,并且在 ProductClassB 中可以有两个构造函数,一个用于产品代码,一个用于单元测试代码,如下所示。

ProductClassB.hpp:

#include <string>
#include "ProductClassA.hpp"

class ProductClassB {
public:
// for unit test
ProductClassB(ProductClassA& ainstance): _ainstance(ainstance) {
}
// for product code
ProductClassB(): _ainstance(ProductClassA()) {
}
~ProductClassB() {
}

public:
int method();

private:
ProductClassA& _ainstance;
};

ProductClassB.cpp:

#include "ProductClassB.hpp"
#include <iostream>

int ProductClassB::method() {
std::cout<< "this is B class method"<<std::endl;
return this->_ainstance.method1("B invoke");
}

ProductClassAMock类是ProductClassA的派生类,其中的方法将以mock的方式重新实现。

ProductClassAMock.hpp:

#include "ProductClassA.hpp"

class ProductClassAMock : public ProductClassA
{
public:
virtual int method1(const std::string& arg1);
virtual int method2(const std::string& arg1);
};

ProductClassAMock.cpp:

#include "ProductClassAMock.hpp"

#include <iostream>
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport.h"

int ProductClassAMock::method1(const std::string& arg1) {
std::cout << arg1 << " ProductClassAMock method1"<<std::endl;
mock().actualCall("method1");
return 0;
}

int ProductClassAMock::method2(const std::string& arg1) {
mock().actualCall("method2");
return 0;
}

ProductClassB 的单元测试将使用测试构造函数在 bInstance 中用模拟类 ProductClassAMock 初始化 _ainstance,然后根据 C++ 多态机制,将 _ainstance.method1() 替换为类 ProductClassAMock 中的 method1。

ProductClassB_unittests.cpp:

#include "ProductClassB.hpp"
#include "ProductClassAMock.hpp"

#include "CppUTest/CommandLineTestRunner.h"
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport.h"

TEST_GROUP(BClassFooTests)
{
void teardown()
{
mock().clear();
}
};

TEST(BClassFooTests, MockAsExpected)
{
mock().expectOneCall("method1");
ProductClassAMock aMockInstance;
ProductClassB bInstance(aMockInstance);
bInstance.method();
mock().checkExpectations();
}

int main(int ac, char** av)
{
return CommandLineTestRunner::RunAllTests(ac, av);
}

产品代码只需使用ProductClassB的产品构造函数,bInstance._ainstance将使用真实的类ProductClassA进行初始化。

main.cpp:

#include "ProductClassB.hpp"

int main() {
ProductClassB bInstance();
bInstance.method();
return 0;
}

如何构建代码:

# build main
g++ ProductClassA.cpp ProductClassB.cpp main.cpp -o main

# build unit test
g++ ProductClassA.cpp ProductClassB.cpp ProductClassAMock.cpp ProductClassB_unittests.cpp -lstdc++ -lCppUTest -lCppUTestExt -o testrunner

参考: