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

mt2*_*t22 2 java testing junit unit-testing 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);
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

ClassB:

class ClassB
{
    ClassB(String configFile)
    {
        // Set up configs.
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

classB仅仅为了测试目的而在内部创建一个空构造函数是不好的做法吗?或者为此重写一个简化的模拟是否更好ClassB

Pab*_*meo 5

对于IoC和依赖注入来说,使用像SpringGuice这样的东西似乎是合适的.

这种方式ClassA只会声明它"需要"一个实例ClassB,并且您的IoC容器将连接并构建所需的依赖项.

但是,如果您不想获得框架,可以通过从中提取接口契约来解决它ClassB,并ClassA依赖于此,并在其构造函数中接收该合同的实现.

例如:

interface SomeContract {
    void someBehavior();
}
Run Code Online (Sandbox Code Playgroud)

ClassB实现它:

class ClassB implements SomeContract {
{
    ClassB(String configFile)
    {
        // Set up configs.
    }

    void someBehavior() {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用它,如:

public class ClassA
{
    private SomeContract implementation;
    public ClassA(SomeContract implementation)
    {
        this.implementation = implementation;
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

最重要的是,您应该尽量避免创建额外的构造函数,仅用于测试目的.


Ufu*_*arı 5

实际上使用无参数构造函数并使用new关键字创建依赖项会使您的类不可测试.由于您无法从构造函数中注入ClassB依赖项,因此您永远不能模拟它并隔离测试中的代码.所有的单元测试都必须测试两个类,这使得它成为一个糟糕的单元测试.相反,您应该将每个依赖项添加为构造函数参数.

这是一个更好的方法:

public class ClassA
{
    // use an abstraction instead of a concrete class
    private IClassB _classB;

    public ClassA(IClassB classB) 
    {
        _classB = classB;
    }
}
Run Code Online (Sandbox Code Playgroud)

这样您就可以模拟IClassB,而不必担心任何ClassB实现细节.