我的A班依赖于B班。 这是代码
//declaration
class A
{
public:
A(B *b);
~A();
void m1();
private:
B *ptr_b;
};
//implementation
A::A(B *b)
{
ptr_b = b;
}
A::~A()
{
delete ptr_b;
}
void A::m1()
{
ptr_b->m2();
}
我想用以下解决方案打破这种依赖性(用于单元测试)。 这是代码
class FakeB : public B
{
public:
FakeB();
~FakeB();
virtual void m2() = 0;
};
class StubB : public FakeB
{
public:
StubB();
~StubB();
void m2();
}
但是当我实例化A类并使用以下代码调用其方法m1()
时A *ptr_a = new A(new StubB);
ptr_a->m1();
方法m1()调用B的方法m2(),因为B的m2()不是虚拟的。 B类是来自另一个模块的遗留代码,我不想更改其代码 但我也不想改变A类的代码。
打破这种依赖的任何解决方案?
答案 0 :(得分:6)
首先,由于A的构造函数中没有delete ptr_b;
,因此在类A的析构函数中有一个new B()
是不好的设计。这意味着每次创建A的实例时,你都是将B对象的所有权转让给A,对于使用不知道内部成员的A的人来说,存在重复delete
的潜在风险。
其次,如果你想给A一个“存根”(或“模拟”或“虚假”)对象而不是“真实B”,B
和FakeB
需要一个通用接口包含来自B的所有方法,A需要作为虚拟方法:
class FakeB : public InterfaceB
和
class B : public InterfaceB
因此A的所有成员函数都可以使用InterfaceB *
类型的参数而不是B *
。然后将FakeB
对象注入A
显然很容易。
不幸的是,这意味着你必须改变B(至少,一点点)。如果这不是一个选项,总有可能通过某个类WrapperB
包装B
(它与经典Adapter pattern中的概念基本相同):
class WrapperB: public InterfaceB
{
B _b;
public:
WrapperB(/* some parameters */) : _b(/* same parameters */){}
// Here you need to implement all methods of
// InterfaceB and delegate them to the original method calls
// of _b. You should give them the same name and signature as
// the corresponding (non-virtual) methods in B.
// For example, if there is a method m2 in B,
// there should be a pure virtual method m2 in InterfaceB, and
// an implementation here like this:
virtual void m2(){ _b.m2(); }
};
WrapperB
将只包含非常简单,直接的方法委托代码,您可以省略单元测试。当你打算将它与A结合使用时,你必须使用WrapperB
代替B
。但你得到的是一个完全可以单元测试的class A
。
另一个(可能更好)变体是以一种方式构造WrapperB类,在这种方式中将B对象的引用从外部注入其中:
class WrapperB: public InterfaceB
{
B& _b;
public:
WrapperB(B& b) :_b(b){}
// implement InterfaceB methods as above
virtual void m2(){ _b.m2(); }
}
您可以像这样使用它:
B b;
A a(WrapperB(b));
FakeB fb;
A a_for_test(fb);
答案 1 :(得分:2)
Merhaba Onur
另一个想法是使用一些预处理器符号在正常和单元测试模式之间切换A类代码。例如:
文件A.hpp
#ifndef UNIT_TESTING
# include "B.hpp" // contains "normal" class B
#else
# include "Testable_B.hpp" // contains "fake" class B, dedicated for unit testing.
#endif
UNIT_TESTING将是一个预处理器符号,只有在构建单元测试时才能启用。
如果文件Testable_B.hpp包含另一个名称而不是“B”的类(例如,Testable_B),您还需要在A类的定义中添加这些指令。缺点是如果需要更多这样的修改,这会使课堂定义变得混乱。
另一种方法是使用typedef:
#ifndef UNIT_TESTING
# include "B.hpp" // contains "normal" class B
#else
# include "Testable_B.hpp" // contains "fake" class B, dedicated for unit testing.
typedef Testable_B B;
#endif
我知道这不是一个非常优雅的解决方案,但如果您不想修改A类代码,也许会发现它很有用。如果您绝对不想对源代码进行任何更改,那么可能是stefaanv的解决方案。
答案 2 :(得分:1)
打破依赖关系的可能性是更改makefile中的include路径并包含你的B类版本。我不知道这是否适用于你的单元测试方案。