让我们假设我要测试这个依赖于难以实例化ClassA
的Java ClassB
:
public class ClassA
{
public ClassA()
{
String configFile = "config_file.xml";
// I have to pass configFile to instantiate ClassB.
// And for example if configFile does not exists in the testing machine?
// Wouldn't it be easier to have an empty constructor for classB to test ClassA?
ClassB classB = new ClassB(configFile);
}
// ...
}
ClassB
:
class ClassB
{
ClassB(String configFile)
{
// Set up configs.
}
// ...
}
仅仅为了测试目的,在classB
内创建一个空构造函数是不好的做法吗?
或者为此重写一个简化的模拟ClassB
是否更好?
答案 0 :(得分:5)
使用像Spring或Guice这样的IoC和依赖注入似乎是合适的场景。
那样ClassA
只会声明它“需要”ClassB
的实例,并且你的IoC容器会连接并构建所需的依赖项。
但是,如果您不想在某种程度上获得框架,可以通过从ClassB
中提取接口合同来解决它,并让ClassA
依赖于此,并接收在其构造函数中实现该合同。
例如:
interface SomeContract {
void someBehavior();
}
让ClassB
实施它:
class ClassB implements SomeContract {
{
ClassB(String configFile)
{
// Set up configs.
}
void someBehavior() {
// ...
}
}
并使用它,如:
public class ClassA
{
private SomeContract implementation;
public ClassA(SomeContract implementation)
{
this.implementation = implementation;
}
// ...
}
这样,在您的单元测试中,您可以传递您在测试中确定的实例,例如模拟,并且您可以相应地进行测试,而无需修改ClassB
的代码。
最重要的是,您应该尽量避免创建其他构造函数,仅用于测试目的。
答案 1 :(得分:5)
实际上使用无参数构造函数并使用new关键字创建依赖项会使您的类不可测试。由于您无法从构造函数中注入ClassB依赖项,因此您永远不能模拟它并隔离测试中的代码。所有的单元测试都必须测试两个类,这使得它成为一个糟糕的单元测试。相反,您应该将每个依赖项添加为构造函数参数。
这是一个更好的方法:
public class ClassA
{
// use an abstraction instead of a concrete class
private IClassB _classB;
public ClassA(IClassB classB)
{
_classB = classB;
}
}
这样您就可以模拟IClassB,而不必担心任何ClassB实现细节。
答案 2 :(得分:1)
关于我的观点,如果传递的配置文件丢失或不存在,我将提供使用默认配置启动的B类选项。
编辑: 众所周知的工件,如Weld / CDI,Hibernate验证器和Arquilluan,也提供带有空配置文件的默认配置。如果没有提到,将应用默认值。