2

假设我要测试这个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

4

3 回答 3

5

使用SpringGuice之类的东西,您的情况似乎适合 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;
    }

    // ...
}

这样,在您的单元测试中,您可以传递您在测试中确定的实例,例如 Mock,并且您可以进行相应的测试,而无需修改ClassB.

底线是,您应该尽量避免创建额外的构造函数等仅用于测试目的。

于 2012-06-17T13:53:26.790 回答
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 实现细节。

于 2012-06-17T13:54:06.230 回答
1

关于我的意见,如果传递的配置文件丢失或不存在,我将为 B 类提供使用默认配置启动的选项。

编辑:众所周知的工件,如 Weld/CDI、Hibernate 验证器和 Arquilluan,也提供了带有空配置文件的默认配置。如果没有提及,则将应用默认值。

于 2012-06-17T13:52:43.337 回答