我有一个类和成员:
class A
{
B obj;
public:
int f(int i){return obj.g(i);}
}
这里
B obj
是一个需要从文件运行时创建的依赖项。在我的 A 类单元测试中,我希望模拟 B obj
,使用函数 g
键入 int(int)
.
如何编写我的测试代码,模拟
B obj
,然后测试A::f
.
非常感谢
你需要使用依赖注入来实现这一点。为此,让类
B
继承自接口,并让类A
持有指向该接口的指针:
class IB
{
public:
virtual void g(int i) = 0;
};
class B : public IB
{
public:
void g(int i) {
// this is your real implementation
}
};
此外,要在类
A
中启用依赖注入,请添加适当的构造函数或setter方法:
class A
{
private:
IB *obj;
public:
A() : obj(nullptr) {}
// You don't need both the constructor and setter, one is enough
A(IB *pB) : obj(pB) {}
// void setB(IB *pB) { obj = pB; }
int f(int i) { return obj->g(i); }
};
现在,在您的生产代码中,您创建一个
B
类的对象并将其传递给A
类对象(假设我们使用构造函数进行注入):
B b;
A a(&b);
在测试阶段,您创建一个模拟类
BMock
并将该类的对象传递给类A
对象:
class BMock : public IB
{
public:
MOCK_METHOD1(g, int(int));
};
TEST(ATests, TestCase1)
{
BMock bmock;
A a(&bmock);
// Now you can set expectations on mock object, for example that g will
// return 100 when it receives 50 as argument
EXPECT_CALL(bmock, g(50)).WillOnce(Return(100));
// Now act by calling A::f, which will in turn call g from mock object,
// and assert on function return value
ASSERT_EQ(a.f(50), 100);
}
你不能用你的代码来做。您已经在
A
类中硬编码了您的依赖项。为了使模拟成为可能,您必须使用一些依赖注入模式。一种可能的方法是有一个指向您的 B
类的指针(更智能),并且在 A
构造函数中,您将获得一个指向 B
的指针,您将使用它来初始化您的内部 B*
。这样你就可以在你的测试中放置一个模拟对象。
为此,在
class A
中使用B的指针而不是对象,并将您的单元测试类(FixtureA
)作为friend class
中的A
。
class A
{
B *obj;
public:
int f(int i){return obj.g(i);}
friend class FixtureA;
};
在
FixtureA.h
你可以有以下代码
class FixtureA
{
public:
void Testf();
}
在
FixtureA.cpp
TEST_F(FixtureA , Testf)
{
Testf();
}
void FixtureA::Testf()
{
A objA;
objA.obj = new BMock();
objA.f(2);
}
在
BMock class
中,您可以模拟g()
功能。
我知道这个问题已经有几年了,但我偶然发现了这个问题,以寻找针对完全相同问题的 better 解决方案。 事实上,我确实有两种可能的解决方案——它们都不是优雅或简单的,但它们确实有效,所以我想在这里分享它们,以防其他人仍然遇到同样的问题。
我假设您在这里使用 gcc 和 ld,但当然这些方法也应该适用于任何其他编译器(至少是第二个编译器)。
其次,我假设,你现在有4个文件,A.hpp、A.cpp、B.cpp和B.hpp,内容如下(当然,如果你把A.hpp和A.cpp结合起来,也会工作...):
A.hpp:
#include "B.hpp"
class A
{
B obj;
public:
int f(int i);
};
A.cpp:
#include "A.hpp"
int A::f(int i){
return obj.g(i);
}
B.hpp:
class B
{
public:
int g(int i);
};
B.cpp 包括 B::g() 的实现。
方法 1 是可能的,如果你有一个文件夹结构,其中 B.hpp 与 A.cpp 不在同一个文件夹中,或者如果你用 #include 替换 A.cpp 中的第 1 行
在这种情况下,您可以创建另一个B.hpp:
#include <gmock/gmock.h>
class B
{
public:
MOCK_METHOD(int, g, (int), ());
};
现在,您必须将命令行更改为类似
g++ A.cpp -I./测试/B -o A.o
新的 B.hpp 位于./Testing/B。此外,在构建测试时,您必须从管道中删除 B.cpp。
请注意,此方法会更改原始 cpp 文件中包含的内容,因此会更改其对象。如果您不够小心,您的测试行为很可能与您的真实系统不同!
方法 2:如果 B.hpp 与 A.cpp 在同一文件夹中并且您有使用 #include "B.hpp" 的代码,则可以使用该方法。在这种情况下,编译器很可能会使用源文件夹中的 B.hpp,而不是使用 -I 标志给出的那个。我认为这种方法更优越,因为它不会改变编译单元 A.o,但另一方面它还有更多工作要做。
创建两个文件: Bmock.hpp:
#include <gmock/gmock.h>
class Bmock{
public:
MOCK_METHOD(int,g,(int),());
};
B.cpp
#include <B.hpp>
#include <Bmock.hpp>
extern Bmock* bmock;
int B::g(int i){
return bmock->g(i);
}
现在,在您的链接步骤中,必须replace您原来的 B.o 文件。
在你的测试套件中:
#include <Bmock.hpp>
#include <A.hpp>
#include <gtest/gtest.h>
Bmock* bmock;
class UT_A:public ::testing::Test{
public:
void SetUp() override{
bmock=new bmock();
}
void TearDown() override{
delete bmock;
}
A uut;
};
TEST_F(UT_A,test_g){
EXPECT_CALL(*bmock,g(5)).WillOnce(Return(3));
ASSERT_EQ(uut.f(5),3);
}
使用此方法,您的编译单元 A.o 可以保持不变,因此应该像在真实系统中一样运行。
我希望这对某人有用!
问候