Java 我能';在不公开私有字段的情况下对类进行单元测试——我的设计有什么问题吗?
我已经写了一些我认为设计得很好的代码,但后来我开始为它编写单元测试,不再那么确定了 事实证明,为了编写一些合理的单元测试,我需要将一些变量访问修饰符从Java 我能';在不公开私有字段的情况下对类进行单元测试——我的设计有什么问题吗?,java,oop,unit-testing,dependency-injection,coding-style,Java,Oop,Unit Testing,Dependency Injection,Coding Style,我已经写了一些我认为设计得很好的代码,但后来我开始为它编写单元测试,不再那么确定了 事实证明,为了编写一些合理的单元测试,我需要将一些变量访问修饰符从private更改为default,即公开它们(仅在包内,但仍然…) 下面是我的代码的一些粗略概述。应该有某种地址验证框架,可以通过不同的方式进行地址验证,例如,通过一些外部Web服务或数据库中的数据或任何其他来源进行验证。所以我有一个模块的概念,它就是这样:一种单独的方法来验证地址。我有一个界面: interface Module { pu
private
更改为default
,即公开它们(仅在包内,但仍然…)
下面是我的代码的一些粗略概述。应该有某种地址验证框架,可以通过不同的方式进行地址验证,例如,通过一些外部Web服务或数据库中的数据或任何其他来源进行验证。所以我有一个模块的概念,它就是这样:一种单独的方法来验证地址。我有一个界面:
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;
}
}
然后,我编写了一个模块
,它使用一些外部Web服务进行验证,并实现如下:
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;
}
}
所以基本上我有这个webservicecfacade
,它是外部web服务的包装器,我的模块调用这个facade,处理它的响应并返回一些框架标准响应
我想测试webservicecmodule
是否正确处理来自外部web服务的响应。显然,我不能在单元测试中调用真正的web服务,所以我在模仿它。但是,为了让模块使用我的模拟web服务,必须从外部访问字段webservice
。它打破了我的设计,我想知道我是否能对此做些什么。显然,不能在init参数中传递facade,因为ModuleFactory
不知道也不应该知道需要它
我已经读到依赖注入可能是这些问题的答案,但我不知道如何解决?我以前没有使用过任何DI框架,所以我不知道在这种情况下是否可以轻松使用它。但也许可以
或者我应该改变我的设计
或者干脆把这个不幸的字段包变成私有的(但是留下一个悲伤的注释,比如//默认可见性以允许测试(哦,好吧…
),感觉不太对)
呸!在写这篇文章时,我突然想到,我可以创建一个webserviceceprocessor
,它将webservicecade
作为构造函数参数,然后只测试webserviceceprocessor
。这将是解决我问题的方法之一。你觉得怎么样?我有一个问题,因为我的webservicecmodule
会有点无用,只是把它的所有工作委托给另一个组件,我会说:一层抽象太远了。是的,你的设计是错误的。您应该在类中执行而不是new…
(也称为“硬编码依赖项”)。无法轻松编写测试是错误设计的完美指示器(请阅读范例中的“倾听您的测试”)
顺便说一句,在这种情况下,使用反射或破坏依赖关系的框架是一种非常糟糕的做法,应该是您的最后手段。是的,您的设计是错误的。您应该在类中执行而不是new…
(也称为“硬编码依赖项”)。无法轻松编写测试是错误设计的完美指示器(请阅读范例中的“倾听您的测试”)
顺便说一句,在这种情况下,使用反射或破坏依赖关系的框架是一种非常糟糕的做法,应该是您的最后手段。是的,您的设计是错误的。您应该在类中执行而不是new…
(也称为“硬编码依赖项”)。无法轻松编写测试是错误设计的完美指示器(请阅读范例中的“倾听您的测试”)
顺便说一句,在这种情况下,使用反射或破坏依赖关系的框架是一种非常糟糕的做法,应该是您的最后手段。是的,您的设计是错误的。您应该在类中执行而不是new…
(也称为“硬编码依赖项”)。无法轻松编写测试是错误设计的完美指示器(请阅读范例中的“倾听您的测试”)
顺便说一句,在这种情况下,使用反射或破坏依赖性的框架是一种非常糟糕的做法,应该是您的最后手段。我同意yegor256所说的,并想指出,您最终陷入这种情况的原因是您为模块分配了多重责任:创建和验证。这违反了标准,有效地限制了您单独测试创建和验证的能力
考虑将“模块”的责任仅限于创建。当他们仅承担此责任时,命名也可以改进:
interface ValidatorFactory {
public Validator createValidator(InitParams params);
}
验证界面变得独立:
interface Validator {
public ValidationResponse validate(Address address);
}
然后,您可以从实现工厂开始:
class WebServiceValidatorFactory implements ValidatorFactory {
public Validator createValidator(InitParams params) {
return new WebServiceValidator(new ProdWebServiceFacade(createParamsForFacade(params)));
}
}
这个工厂代码很难进行单元测试,因为它显式地引用了prod代码,所以请保持这个impl非常简洁。将任何逻辑(如createParamsForFacade
)放在旁边,以便可以单独测试它
web服务验证器本身只负责验证,并将外观视为一个依赖项,遵循以下原则:
class WebServiceValidator implements Validator {
private final WebServiceFacade facade;
public WebServiceValidator(WebServiceFacade facade) {
this.facade = facade;
}
public ValidationResponse validate(Address address) {
WebService wsResponse = webservice.validate(address);
ValidationResponse reponse = proccessWsResponse(wsResponse);
return response;
}
}
由于WebServiceValidator
不再控制其依赖项的创建,测试变得轻而易举:
@Test
public void aTest() {
WebServiceValidator validator = new WebServiceValidator(new MockWebServiceFacade());
...
}
通过这种方式,您有效地反转了对依赖项创建的控制:控制反转(IoC)
哦,顺便说一下,先写你的测试。这样,您自然会倾向于可测试的解决方案,这通常也是最好的设计。我认为这是因为测试需要模块化,而模块化恰好是良好设计的标志。我同意