单元测试最佳实践:空构造函数或模拟对象?

时间:2012-06-17 13:31:37

标签: java unit-testing testing junit mocking

让我们假设我要测试这个依赖于难以实例化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是否更好?

3 个答案:

答案 0 :(得分: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;
    }

    // ...
}

这样,在您的单元测试中,您可以传递您在测试中确定的实例,例如模拟,并且您可以相应地进行测试,而无需修改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,也提供带有空配置文件的默认配置。如果没有提到,将应用默认值。