所以,我已经实现了我的业务类,我通过构造函数传递所有依赖项,所以我可以嘲笑那些并轻松地对它们进行单元测试。到目前为止,这很有用,但是,有一点,我需要从这些对象中创建一个对象图。为此,我正在使用一个静态工厂(遗憾的是我不能使用DI框架)。例如:
public class FooBar {
public FooBar(Foo foo, Bar bar) {
this.foo = foo;
this.bar = bar;
}
}
public class Foo {
public Foo() {}
}
public class Bar {
public Bar(Foo foo) {
this.foo = foo;
}
}
public class GraphFactory {
public static FooBar newFooBar() {
Foo foo = new Foo();
Bar bar = new Bar(foo);
return new FooBar(foo, bar);
}
}
所以,我无法真正测试GraphFactory(无法模拟依赖项),这有点好(这里没有做太多工作)。但是如果图形的构造更复杂,即它涉及查找某些属性,进行JNDI查找等等呢?
我还应该为此编写单元测试吗?我的班级设计可能会被打破吗?单元测试的目的不是单独测试类吗?
答案 0 :(得分:2)
如果newFooBar
方法必须更复杂,您可以将除Foo
和Bar
的创建之外的所有内容重构为包私有初始化方法。然后编写初始化方法的测试。
public class GraphFactory {
public static FooBar newFooBar() {
Foo foo = new Foo();
Bar bar = new Bar(foo);
FooBar toReturn = new FooBar(foo, bar);
initialise( toReturn );
return toReturn;
}
static void initialise( FooBar toInitialise ){
// some stuff here that you can test
}
}
答案 1 :(得分:2)
您不能模拟或存根静态方法调用,但您可以在没有模拟的情况下为工厂编写单元测试。
但是,为什么要使用静态工厂方法?如果要以静态方式访问它,可能更好地将工厂存储在静态变量中。
答案 2 :(得分:2)
这种设计没有任何问题,而且完全可以测试。如果我们专注于工厂的单一责任 - 它负责组装对象图 - 对此的测试是直截了当的:
在上面的例子中,foo,bar和foobar是测试的结果,不需要模拟。
如果对象图的程序集更复杂,并且您需要其他服务来获取数据并检查应用程序设置,请猜猜会发生什么?这些依赖项通过其构造函数传递到工厂。您应该模拟这些依赖项以将工厂与其依赖项的依赖项隔离开来。您工厂的消费者将通过他们的构造者获得一个完全连线的工厂;或者工厂在应用程序启动时组装,并以单件等形式提供。
这就是DI的工作原理。这听起来很奇怪,因为现在你不得不担心工厂是如何被创造的(以及谁创造了这个对象等,乌龟一直都是如此),这是一个完全自然的反应。
这就是为什么我们有DI框架来组装复杂的对象图。在精心设计的DI应用程序中,什么都不应该知道图表是如何组装的。类似于这种设计,任何人都不应该知道DI框架。
该规则的唯一例外是......(鼓点)......工厂对象!
如果正在解析的对象在其构造函数中使用DI,则大多数DI框架会将DI容器注入对象。这极大地简化了工厂的管道,并且满足了我们的设计原则:除了我们的工厂之外,没有人知道如何构造我们的对象,并且我们已经将DI容器的知识封装到负责对象组装的应用程序的关键区域。
呼。现在,如果您仍在继续,这将如何改变我们的测试?它不应该,至少不要太多:步骤#1稍微改变,用模拟对象填充DI容器,然后使用DI容器构建工厂。
作为一种品味,有些人喜欢在测试期间使用自动模拟DI容器,这会在请求项目时自动生成模拟依赖项。这可以消除大部分痛苦。
答案 3 :(得分:1)
你应该看看Dependecy Injection,它可以让你在正确使用时摆脱工厂。我推荐你这个视频作为介绍,它帮助了我很多: http://code.google.com/p/google-guice/
这是谷歌图书馆Guice的介绍,但它会帮助你理解DI。然后你会看到如何用注释替换所有那些'new',让图书馆为你完成所有的工作。有些人会推荐Sring,但我觉得Guice对初学者更友好。我只是希望这不会再次引发Guice vs Spring的讨论:D