在创建方法时,是否应该将在该方法中实例化的每个对象作为参数传入,以便可以在单元测试中模拟这些对象?
我们在工作中有很多方法,没有相关的单元测试,也没有回顾性地编写测试;我们发现在这些方法中实例化了很多对象。
我们的一个选择是将我们当前的方法重构为更多类似单位的方法,并减少每个方法的责任数量。这可能是一个漫长的过程,但对我们来说肯定是一个很大的好处。
你怎么看?是否应将在方法中实例化的所有对象作为参数传递?答案 0 :(得分:7)
也许不是所有对象,但是你注入单位的对象越多,关注点的分离就越好,所以我肯定会建议你向这个方向移动。
您不必将所有对象作为方法参数传递。通过构造函数注入将协作者注入类中通常是更好的设计。这可以使您的界面保持清洁,同时您的实施可以导入所需的协作者。
假设你的原始实现看起来像这样:
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的实现。
答案 1 :(得分:6)
第一部分是一个有点问题。这就像问“当我的车里的行人跑过来时,我应该把双手放在方向盘上吗?”
实例化大量其他对象的方法几乎肯定做得太多了。具有大量这些方法的类可能不遵循单一责任原则。
然而,使代码可测试的关键方法之一是使用IoC(控制反转),其中将类的依赖(或方法)传递给它,而不是要求它们的类。这样可以更容易测试,因为你可以传入模拟。
所以简短的回答是“是”,传入你的依赖关系并查看一个好的dependency injection组件。长的答案是“是的,但不要这样做”。 DI框架可能会强制您将依赖项传递给对象而不是方法,并且您会发现要确保限制这些依赖项 - 这是一件好事。
当然,重构以减少依赖性是好的。缩短你做一件事的方法几乎从来都不是坏事。我强烈同意这是长期收益,只要你能负担短期费用。
答案 2 :(得分:2)
只是观察:你在谈论方法,而我更喜欢谈论课程。
这个问题没有一般答案。有时,将类的创建与用法分离是非常重要的。
考虑:
另一个技巧是:使注射可选。这意味着,当您在构造函数中传递实例时,将使用它。如果没有,则由类本身创建一个新实例。这在处理遗留代码时以及没有依赖注入框架时非常方便。
答案 3 :(得分:2)
[免责声明:我在Typemock工作]
你有三个选择 - 其中两个需要一些重构:
可测试性设计并不总是一个好的做法,尤其是对于现有项目而言
你已经写了一些代码。
因此,如果你开始干净或有一个小项目,也许可以将对象作为参数传递给类的构造函数,只要你没有太多的参数。
- 如果您使用IoC容器。
如果您不想更改所有现有代码和/或不希望以某种方式设计代码以使其“更易于测试”(可能导致一些看似奇怪的代码) - 使用Isolator(或类似工具)如果你正在使用Java)。
答案 4 :(得分:2)
仅在编写单元测试时模拟妨碍的对象。如果方法创建了一个对象来执行其任务,您可以探测其结果,那么就不需要模拟所创建的对象的类。
当您想要将一个类与其他类隔离时,请使用mock。使用mock让测试远离
将对象与其构造分开使用
答案 5 :(得分:1)
在某些时候,你不会放弃从另一个对象创建,但你应该将软件编写为良好的设计原则。例如。 SRP,DI等..
如果您有许多依赖项,您可能会发现IoC容器将帮助您管理它们。
在处理遗留代码时,您可能会发现阅读Michael Feather的Working Effectively with Legacy code很有用。这本书有很多关于如何让你的系统得到测试的技术。
答案 6 :(得分:0)
我不确定你正在使用哪种语言/工具,但是它可以用来实现它的方式就是模拟构造函数,就像这样:
@user = mock_model(User)
User.stub(:create).and_return(@user)
因此,从现在开始,如果您调用User.create(User是“Class”),它将始终返回您预定义的@user变量,从而可以完全控制单元测试中使用的数据。
现在您已确切知道测试中的数据,您可以开始存根对象的实例方法,以确保它们返回可用于测试方法的正确数据。
答案 7 :(得分:0)
你应该支持不带参数的方法,然后是一个参数,两个,最后三个。任何超过3的东西都是代码味道。要么在传入的所有参数中都有一个等待发现的类,要么类/方法试图做太多。
在传入依赖关系方面,您可以使用构造函数注入,但随着时间的推移,当您慢慢转向必须传递整个对象图时,这会变得难以处理。我的建议是尽快使用IoC容器,避免痛苦。
答案 8 :(得分:0)
我会尝试在类上使用依赖注入,而不是让类方法创建对象(如所选答案推荐的那样)。如果没有意义,可以考虑创建一个生成正在创建的对象的工厂类。然后,您可以通过依赖注入传入该工厂。
答案 9 :(得分:0)
我更喜欢模拟对象周围的所有内容,并根据对相关对象的调用和响应来定义正在测试的对象的行为。
为了有效地做到这一点,通常要求您的接口处于语义级别而不是实现级别。