有没有办法打破单元测试的这种依赖性?
我的 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 类的代码。
有什么解决方案可以打破这种依赖性吗?
My class A is dependent to class B.
Here is the code
//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();
}
I want to break this dependency(for unit testing) with the following solution.
Here is the code
class FakeB : public B
{
public:
FakeB();
~FakeB();
virtual void m2() = 0;
};
class StubB : public FakeB
{
public:
StubB();
~StubB();
void m2();
}
But when I instantiate class A and call its method m1() with the following code
A *ptr_a = new A(new StubB);
ptr_a->m1();
Method m1() calls B's method m2() because B's m2() is not virtual.
class B is legacy code from another module I do not want to change its code
but also I do not want to change class A's code.
Any solution to break this dependency?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
首先,在类 A 的析构函数中使用
delete ptr_b;
是一个糟糕的设计,因为 A 的构造函数中没有new B()
。这意味着每次创建 A 的实例后,您会将 B 对象的所有权转移给 A,这会给您带来潜在的风险,即使用 A 且不了解其内部结构的人会出现重复删除
的情况。其次,如果您想给 A 一个“存根”(或“模拟”或“假”)对象而不是“真实的 B”,则需要
B
和FakeB
一个公共接口,包含 B 中 A 需要的所有方法作为虚拟方法:因此
A 的所有成员函数都可以使用
InterfaceB *
类型的参数,而不是B *
。然后将FakeB
对象注入A
显然很容易。不幸的是,这意味着你必须改变 B (至少,一点点)。如果这不是一个选项,那么总是有可能通过某个类
WrapperB
来包装 B(它与经典适配器模式中的想法基本相同):
WrapperB 将仅包含非常简单、直接的方法委托代码,您可以省略单元测试。当您要将它与 A 结合使用时,您必须使用
WrapperB
而不是B
。但是您得到的是一个完全可单元测试的class A< /代码>。
另一个(也许更好)变体是以从外部注入对 B 对象的引用的方式构造 WrapperB 类:
您可以像这样使用它:
First, it is bad design having a
delete ptr_b;
in the destructor of class A since there is nonew B()
in the constructor of A. That means every time an instance of A is created, you are transfering ownership of the B object to A, leaving you with the potential risk of a duplicatedelete
for someone using A who does not know the internals.Second, if you want to give A a "stub" (or "mock", or "fake") object instead of a "real B",
B
andFakeB
need a common interface containing all methods from B which A needs as virtual methods:and
so all member functions of A can use parameters of type
InterfaceB *
instead ofB *
. Then injecting aFakeB
object intoA
gets obviously easy.Unfortunately, that would mean you have to change B (at least, a little bit). If that is not an option, there is always the possibility of wrapping B by some class
WrapperB
(it is mostly the same idea as in the classic Adapter pattern):
WrapperB
will contain only very simple, straightforward method delegation code for which you can omit unit tests. And you have to useWrapperB
instead ofB
when you are going to use it in conjunction with A. But what you get is a perfectly unit testableclass A
.Another (perhaps even better) variant is constructing the WrapperB class in a manner where you inject a reference to a B object from outside into it:
You can use it just like this:
Merhaba Onur
另一个想法是使用一些预处理器符号在正常模式和单元测试模式之间切换 A 类代码。例如:
文件 A.hpp
UNIT_TESTING 将是一个预处理器符号,仅在构建单元测试时才启用它。
如果文件 Testable_B.hpp 包含名称不是“B”的类(例如 Testable_B),您还需要在类 A 的定义中添加这些指令。缺点是,如果需要更多此类修改,这将导致使类定义变得混乱。
另一种方法是使用 typedef:
我知道这不是非常优雅的解决方案,但如果您不想修改 A 类代码,也许您会发现它很有用。如果您绝对不想对源代码进行任何更改,那么 stefaanv 的解决方案可能是最佳选择。
Merhaba Onur
Another idea would be to use some preprocessor symbols to switch class A code between normal and unit-testing mode. For example:
File A.hpp
UNIT_TESTING would be a preprocessor symbol which you would enable only when building the unit test.
In case if file Testable_B.hpp contains class with another name than "B" (for example, Testable_B) you would also need to add these directives in the definition of class A. The drawback is that if more such modifications were needed, this would make a mess in class definition.
Yet another way would be to use typedef:
I know it is not very elegant solution, but maybe you will find it useful if you don't want to modify class A code. In case you absolutely don't want to make any changes to the source code, then probably stefaanv's solution is the way to go.
打破依赖性的一种可能性是更改 makefile 中的包含路径并包含您的 B 类版本。我无法判断这是否适用于您的单元测试方案。
A possibility to break the dependency is to change the include path in your makefile and to include your version of class B. I can't tell if this works in your unit testing scheme.