pht*_*ier 5 language-agnostic unit-testing dependency-injection
我一直在阅读Misko Hevery 关于依赖注入的经典 文章,基本上是"将对象图创建代码与代码逻辑分离".
主要想法似乎是"摆脱'新'运营商',将它们放入专用对象('工厂')并注入你所依赖的一切."
现在,我似乎无法理解如何使用由几个其他组件组成的对象,并且其工作是将这些组件隔离到外部世界.
一个View类,表示几个字段和一个按钮的组合.所有组件都依赖于图形ui上下文,但您希望将其隐藏在每个子组件的接口后面.
所以像(在伪代码中,语言并不重要我猜):
class CustomView() {
public CustomView(UIContext ui) {
this.ui = ui
}
public void start() {
this.field = new Field(this.ui);
this.button = new Button(this.ui, "ClickMe");
this.button.addEventListener(function () {
if (field.getText().isEmtpy()) {
alert("Field should not be empty");
} else {
this.fireValueEntered(this.field.getText());
}
});
}
// The interface of this component is that callers
// subscribe to "addValueEnteredListener"..)
public void addValueEnteredListener(Callback ...) {
}
public void fireValueEnteredListener(text) {
// Would call each listeners in turn
}
}
Run Code Online (Sandbox Code Playgroud)
来电者可以这样做:
// Assuming a UIContext comes from somewhere...
ui = // Wherever you get UI Context from ?
v = new CustomView(ui);
v.addValueEnteredListener(function (text) {
// whatever...
});
Run Code Online (Sandbox Code Playgroud)
现在,这段代码有三个"新"运算符,我不确定Misko(以及其他DI支持者)提倡哪一个摆脱,或者如何.
我不认为这里的想法是实际注入 Field和Button的实例,这可以这样做:
class CustomView() {
public CustomView(Field field, Button button) {
this.field = field;
this.button = button;
}
public void start() {
this.button.addEventListener(function () {
if (field.getText().isEmtpy()) {
alert("Field should not be empty");
} else {
this.fireValueEntered(this.field.getText());
}
});
}
// ... etc ...
Run Code Online (Sandbox Code Playgroud)
这使得组件的代码更轻,当然,它实际上隐藏了UI的概念,因此MetaForm组件在可读性和可测试性方面明显得到了改进.
但是,现在客户负担创建这些东西:
// Assuming a UIContext comes from somewhere...
ui = // wherever ui gets injected from
form = new Form(ui);
button = new Button(ui);
v = new CustomView(form, button);
v.addValueEnteredListener(function (text) {
// whatever...
});
Run Code Online (Sandbox Code Playgroud)
这对我来说听起来真的很麻烦,特别是因为客户知道这个课程的所有内容,这听起来很愚蠢.
文章似乎提倡的是注入工厂来创建组件元素.
class CustomView() {
public CustomView(Factory factory) {
this.factory = factory;
}
public void start() {
this.field = factory.createField();
this.button = factory.createButton();
this.button.addEventListener(function () {
if (field.getText().isEmtpy()) {
alert("Field should not be empty");
} else {
this.fireValueEntered(this.field.getText());
}
});
}
// ... etc ...
Run Code Online (Sandbox Code Playgroud)
然后一切对调用者来说都很好,因为它只需要从某个地方获取工厂(而且这个工厂将是唯一知道UI上下文的人,所以请求解耦.)
// Assuming a UIContext comes from somewhere...
factory = // wherever factory gets injected from
v = new CustomView(factory);
v.addValueEnteredListener(function (text) {
// whatever...
});
Run Code Online (Sandbox Code Playgroud)
一个可能的缺点是,在测试MetaForm时,您通常必须使用"Mock"工厂......创建Field&Button类的Mocks版本.但显然还有另一个缺点......
工厂有多大?如果您严格遵循该模式,那么您希望在运行时在应用程序中创建的每个frigging组件(通常是UI的情况,对吧)必须在至少一个工厂中获得自己的createXXXXX方法.
因为现在你需要:
我可以看到一些策略来简化这个:
我几乎可以听到来自C家伙的所有笑话,没有Java应用程序可以做任何事情而不需要调用ApplicationProcessFactoryCreator.createFactory().createProcess().startApplication()无意义链...
所以我的问题是:
假设我决定使用依赖注入,使用类似guice的框架.我最终会编写这样的代码:
class CustomView
@Inject
private Field fiedl;
@Inject
private Button button;
public void start() {
this.button.addEventListener(....
// etc...
Run Code Online (Sandbox Code Playgroud)
然后什么 ?
我的"组合根"将如何利用它?我可以通常不为Field和Button配置一个"单例"(带有小写的's',如'一个类的单个实例)(因为我想创建它们的实例作为MetaForm的实例?
使用Provider是没有意义的,因为我的问题不是我想要创建哪个按钮实例,而是我想最近创建它,使用一些只对这个表单有意义的配置(例如它的文本) .
对我来说,DI是不会帮助,因为我是新-ING 部分的我的组件,而不是依赖.我想我可以将任何子组件转换为依赖项,并让框架注入它们.只是注入子组件对我来说看起来非常人为和直接,在这种情况下......所以,我一定要错过一些东西;)
编辑
特别是,我的问题是我似乎无法理解你将如何测试以下场景:
"当我点击按钮时,如果Field为空,则应该出现错误".
如果我注入按钮,这是可行的,这样我可以手动调用它的"fireClicked"事件 - 但感觉有点傻.
另一种方法是只执行view.getButton().fireClicked(),但这看起来有点难看......
好吧,你可以使用一些 DI 框架(Spring 或 Guice)并完全摆脱工厂方法。只需在字段/构造函数/设置方法上添加一些注释,DI 框架就会完成工作。在单元测试中使用模拟框架。