我们在工作中有很多没有关联单元测试的方法。回过头来编写测试时,我们发现这些方法内部实例化了相当多的对象。
我们的一个选项是将当前方法重构为更像单元测试的方法,并减少每个方法的职责数量。这可能是一个漫长的过程,但在未来肯定会对我们产生很大的好处。
你认为呢?在方法内实例化的所有对象都应该作为参数传递吗?
也许不是所有的对象,但你注入到单元测试中的对象越多,你的关注点分离就会变得越好,所以我强烈建议你朝这个方向发展。
你不必将所有对象作为方法参数传递。通过构造函数注入协作者到类中通常是更好的设计。这样可以保持接口的清晰,而实现可以导入所需的协作者。
假设你最初的实现看起来像这样:
public class Foo
{
public Ploeh DoStuff(Fnaah f)
{
var bar = new Bar();
return bar.DoIt(f);
}
}
这可以改为看起来像这样:
public class Foo
{
private readonly IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
public Ploeh DoStuff(Fnaah f)
{
return this.bar.DoIt(f);
}
}
bar
从Bar的实例更改为IBar的实例,从而使得Foo与IBar的具体实现分离。这种重构往往会使您编写和维护单元测试变得更加简单,因为现在您可以独立地变化Foo和Bar的实现。[免责声明:我在Typemock工作]
你有三个选择 - 其中两个需要一些重构:
为了可测试性而设计并不总是一个好习惯,特别是对于已经编写了一些代码的现有项目。
因此,如果你从零开始或者有一个小项目,也许将对象作为参数传递给类的构造函数是可以接受的,只要你没有太多的参数。
- 如果你使用IoC容器。
如果你不想改变你所有现有的代码和/或不想设计你的代码以使其“更具可测试性”(可能会导致一些奇怪的代码),请使用Isolator(或类似工具,如果你使用Java)。
只有在编写单元测试时遇到了干扰的对象,才需要模拟这些对象。如果一个方法创建一个对象来执行其任务,并且您可以验证其结果,那么就不需要模拟所创建的对象的类。
当您想要将一个类与其他内容隔离开来时,请使用模拟。使用模拟可使测试远离以下内容:
将对象的使用与它们的构造分开。
在某些时候,你不得不从一个对象创建另一个对象,但你应该按照良好的设计原则编写软件。例如 SRP、DI 等。
当你有许多依赖关系时,你可能会发现 IoC 容器可以帮助你管理它们。
处理遗留代码时,你可能会发现阅读 Michael Feather 的 《Working Effectively with Legacy code》 很有用。这本书有很多技巧,可以帮助你测试系统。
我不确定您使用的是哪种语言/工具,但在Rails中可以这样做,即模拟构造函数:
@user = mock_model(User)
User.stub(:create).and_return(@user)
所以从现在开始,在你的测试中如果你调用 User.create(User 是“类”),它将始终返回你预定义的 @user 变量,让你完全控制在单元测试中使用哪些数据。
既然你知道了测试中确切的数据,你可以开始存根对象的实例方法,以确保它们返回适当的数据,这样你就可以用来测试你的方法。
我更喜欢对一个对象周围的所有东西进行模拟,并根据与相关对象的调用和响应来定义正在测试的对象的行为。
要有效地做到这一点,通常需要将接口放在语义级别而不是实现级别。
你应该优先考虑不带参数的方法,其次是一个参数、两个参数,最后是三个参数。任何需要超过三个参数的方法都是代码异味。要么在传递的所有参数中存在等待被发现的类,要么这个类/方法试图做太多事情。
关于传递依赖项,你可以使用构造函数注入,但随着时间的推移,当你慢慢地开始传递整个对象图时,这会变得难以管理。我的建议是尽早转向使用IoC容器,避免痛苦。