c++ 如何模拟依赖的成员变量?

0qx6xfy6  于 2023-05-02  发布在  其他
关注(0)|答案(4)|浏览(126)

我有一个类和成员:

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
多谢了。

hrirmatl

hrirmatl1#

您需要使用依赖注入来实现这一点。为此,让类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);

在测试阶段,创建一个mock类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);
}
b4wnujal

b4wnujal2#

你不能用你的代码来做。您已经在A类中硬编码了您的依赖项。为了使mocking成为可能,您必须使用一些依赖注入模式。一种可能的方法是有一个指向B类的指针(更好的智能),在A构造函数中,您将获得一个指向B的指针,您将使用该指针初始化内部B*。这样,您就可以在测试中放置一个模拟对象。

3pvhb19x

3pvhb19x3#

为此,取一个指针B,而不是class A中的object,并将单元Test class(FixtureA)设置为A中的friend class

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()函数。

rjee0c15

rjee0c154#

我知道这个问题已经有几年的历史了,但我偶然发现了这个问题,为同样的问题寻找一个更好的解决方案。事实上,我确实有两个可能的解决方案-没有一个是优雅或简单的,但他们确实工作,所以我想在这里分享他们,以防别人仍然有同样的问题。
我假设你在这里使用的是gcc和ld,当然这些方法也应该适用于任何其他编译器(至少是第二个)。
其次,我假设你现在有4个文件,Ahpp,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或者替换A中的第1行。cpp by #include<B.hpp>(我认为这是编译器/预处理器相关的,但我不确定。Afaik,gcc总是搜索进去。首先,如果您使用“)
在这种情况下,您可以创建另一个B。hpp:

#include <gmock/gmock.h>
class B
{
public:
    MOCK_METHOD(int, g, (int), ());
};

现在,您必须将命令行更改为类似
g++ A. cpp-I./测试/B -o A。奥
新的B。HPP位于。/Testing/B。此外,您必须删除B。cpp从你的管道时构建测试。
请注意,此方法会更改原始cpp文件中包含的内容,因此会更改其对象。如果您不够小心,您的测试行为很有可能与您的真实的系统不同!
方法2:如果B,你可以使用那个。hpp与A在同一个文件夹中。cpp,你有一个使用#include“B的代码。在这种情况下,编译器很可能使用B.hpp,而不是带有-I标志的。我认为这种方法上级,因为它不改变编译单元A。但另一方面,还有更多的工作要做。
创建两个文件: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);
}

现在,在你的链接步骤中,必须替换你原来的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可以保持不变,因此应该表现得像在真实的系统中一样。
我希望这对某人有用!
欢迎光临

相关问题