【发布时间】:2013-10-11 05:25:21
【问题描述】:
我编写了一些我认为设计得非常好的代码,但后来我开始为它编写单元测试并不再那么确定了。
事实证明,为了编写一些合理的单元测试,我需要将我的一些变量访问修饰符从private 更改为default,即公开它们(仅在包内,但仍然...) .
以下是我的相关代码的粗略概述。应该有某种地址验证框架,可以通过不同的方式进行地址验证,例如通过一些外部网络服务或数据库中的数据或任何其他来源来验证它们。所以我有一个Module 的概念,就是这样:一种验证地址的单独方法。我有一个界面:
interface Module {
public void init(InitParams params);
public ValidationResponse validate(Address address);
}
有某种工厂,它根据请求或会话状态选择合适的模块:
class ModuleFactory {
Module selectModule(HttpRequest request) {
Module module = chooseModule(request);// analyze request and choose a module
module.init(createInitParams(request)); // init module
return module;
}
}
然后,我写了一个Module,它使用一些外部网络服务进行验证,并像这样实现它:
WebServiceModule {
private WebServiceFacade webservice;
public void init(InitParams params) {
webservice = new WebServiceFacade(createParamsForFacade(params));
}
public ValidationResponse validate(Address address) {
WebService wsResponse = webservice.validate(address);
ValidationResponse reponse = proccessWsResponse(wsResponse);
return response;
}
}
所以基本上我有这个WebServiceFacade,它是外部 Web 服务的包装器,我的模块调用这个外观,处理它的响应并返回一些框架标准的响应。
我想测试WebServiceModule 是否正确处理来自外部 Web 服务的响应。显然,我不能在单元测试中调用真正的 Web 服务,所以我在嘲笑它。但话又说回来,为了让模块使用我的模拟 Web 服务,必须可以从外部访问字段 webservice。它破坏了我的设计,我想知道我是否可以做些什么。显然,facade 不能传入 init 参数,因为ModuleFactory 不知道也不应该知道需要它。
我读过依赖注入可能是解决此类问题的方法,但我不知道如何解决?我之前没有使用过任何 DI 框架,比如Guice,所以我不知道在这种情况下是否可以轻松使用它。但也许可以?
或者也许我应该改变我的设计?
或者把它搞砸并将这个不幸的字段包设为私有(但留下像// default visibility to allow testing (oh well...) 这样的悲伤评论不感觉正确)?
呸!在我写这篇文章的时候,我突然想到,我可以创建一个WebServiceProcessor,它将WebServiceFacade 作为构造函数参数,然后只测试WebServiceProcessor。这将是我的问题的解决方案之一。你怎么看待这件事?我对此有一个问题,因为那样我的WebServiceModule 将有点没用,只是将其所有工作委托给另一个组件,我会说:一层抽象太远了。
【问题讨论】:
-
许多依赖注入框架使用反射来允许注入私有成员。有趣的是,Google Guava 包含一个注解
@VisibleForTesting。 -
@EricJablow 很酷,我不知道这个注释,但它比注释要好得多,因为无论如何我已经在使用 Guava
-
@smajlo 谢谢,这当然是开始寻找答案的好地方。但我希望更多关于我的特定设计及其与单元测试/干净代码的关系的反馈。
标签: java oop unit-testing dependency-injection coding-style