为什么单身人士课难以测试?

cod*_*123 8 java singleton

有效的Java Item 3(使用私有构造函数或枚举类型强制执行singleton属性)注意到:

使类成为单例可能会使测试其客户端变得困难,因为除非它实现了作为其类型的接口,否则不可能将模拟实现替换为单例.

出于测试目的,为什么实例化单个单例实例并测试其API是不够的?这不是客户会消费的吗?引用似乎意味着测试单例将涉及"模拟实现",但为什么这是必要的?

我已经看到了各种"解释",或多或少地重述了上面的引用.有人可以进一步解释这一点,最好用代码示例吗?

mcl*_*sen 10

如果你的单身人士在数据库上执行操作或将数据写入文件怎么办?你不希望在单元测试中发生这种情况.您可能希望模拟对象以在内存中执行某些操作,以便您可以验证它们而不会产生永久的副作用.单元测试应该是自包含的,不应该创建与数据库的连接,也不应该对可能发生故障的外部系统执行其他操作,然后导致单元测试因不相关的原因而失败.

伪java的例子(我是C#dev):

public class MySingleton {

    private static final MySingleton instance = new MySingleton();

    private MySingleton() { }

    public int doSomething() {
        //create connection to database, write to a file, etc..
        return something;
    }

    public static MySingleton getInstance() {
        return instance;
    }
}

public class OtherClass {

        public int myMethod() {
            //do some stuff
            int result = MySingleton.getInstance().doSomething();

            //do some other suff
            return something;
        }
}
Run Code Online (Sandbox Code Playgroud)

为了测试myMethod我们必须进行实际的数据库调用,文件操作等

@Test
public void testMyMethod() {
    OtherClass obj = new OtherClass();

    //if this fails it might be because of some external code called by 
    //MySingleton.doSomething(), not necessarily the logic inside MyMethod()

    Asserts.assertEqual(1, obj.myMethod());
}
Run Code Online (Sandbox Code Playgroud)

如果MySingleton是这样的话:

public class MyNonSingleton implements ISomeInterface {

    public MyNonSingleton() {}

    @Override
    public int doSomething() {
        //create connection to database, write to a file, etc..
        return something;
    }

}
Run Code Online (Sandbox Code Playgroud)

然后你可以将它作为依赖注入到MyOtherClass中,如下所示:

public class OtherClass {

    private ISomeInterface obj;

    public OtherClass(ISomeInterface obj) {
        this.obj = obj;
    }

    public int myMethod() {
        //do some stuff
        int result = obj.doSomething();

        //do some other stuff
        return something;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样测试:

@Test
public void TestMyMethod() {
    OtherClass obj = new OtherClass(new MockNonSingleton());

    //now our mock object can fake the database, filesystem etc. calls to isolate the testing to just the logic in myMethod()

    Asserts.assertEqual(1, obj.myMethod());
}
Run Code Online (Sandbox Code Playgroud)

  • 但似乎`MySingleton`和`MyNonSingleton`之间的主要区别在于`MyNonSingleton`实现了一个作为类型的接口,而不是`MyNonSingleton`不是单例.如果`MySingleton`仍然是单例,但是实现了`ISomeInterface`,那么你是否能够以相同的方式测试它(使用实现相同接口的`MockSingleton`)? (4认同)

lot*_*lot 8

我个人认为这个说法是完全错误的,因为它假设单例对于单元测试是不可替换(可模拟)的。相反。例如,在 Spring 的依赖注入中,单例实际上是 DI 组件的默认模型。单例和依赖注入并不相互排斥,上面的陈述试图暗示这一点。

我同意任何不能模拟的东西都会使应用程序更难以测试,但是没有理由假设单例比应用程序中的任何其他对象更难模拟

可能的问题是,单例是一个全局实例,当它可能处于太多不同的状态时,单元测试可能会由于单例状态的变化而显示不可预测的结果。但对此有一些简单的解决方案 - 模拟你的单例并让你的模拟具有更少的状态。或者以这样的方式编写测试,在依赖于它的每个单元测试之前重新创建(或重新初始化)该单例。或者,最好的解决方案是测试您的应用程序的单例的所有可能状态。最终,如果现实需要多种状态,例如数据库连接(断开/连接/连接/错误/...),那么无论您是否使用单例,您都必须处理它。