嘲笑一个单身人士课程

我最近读到,制作一个单独的课程使得不可能嘲笑课程的对象,这使得测试它的客户变得很困难。 我无法立即明白其根本原因。 有人可以解释一下,嘲笑一个单身人士课程是不可能的吗? 此外,还有更多的问题与创建一个单独的类相关吗?


当然,我可以写一些不使用单例的东西,他们是邪恶的,使用Guice / Spring /不管怎样,但是首先,这不会回答你的问题,其次,当你使用遗留代码的时候,你有时不得不处理单例。例。

所以,我们不要讨论singleton的好坏(还有另一个问题),但让我们看看在测试过程中如何处理它们。 首先,我们来看看单例的一个常见实现:

public class Singleton {
    private Singleton() { }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    public String getFoo() {
        return "bar";
    }
}

这里有两个测试问题:

  • 构造函数是私有的,所以我们不能扩展它(并且我们无法控制测试中实例的创建,但是,这是单例的重点)。

  • getInstance是静态的,所以很难在使用单例的代码中注入伪造对象而不是单例对象。

  • 对于基于继承和多态的嘲讽框架来说,这两点显然都是很大的问题。 如果你有代码的控制权,一种选择是通过添加一个允许调整内部字段的setter来使你的单例更“可测试”,如学习停止担心和爱Singleton(你甚至不需要嘲讽在这种情况下框架)。 如果你不这样做,基于拦截和AOP概念的现代嘲讽框架可以克服前面提到的问题。

    例如,Mocking Static Method Calls显示了如何使用JMockit Expectations来模拟单例。

    另一种选择是使用PowerMock,它是Mockito或JMock的扩展,它允许模拟通常不像静态,最终,私有或构造函数方法那样可玩的东西。 你也可以访问一个类的内部。


    嘲笑单身人士的最好方法是根本不使用它们,或者至少不是传统意义上的。 您可能需要查看的一些做法是:

  • 编程到接口
  • 依赖注入
  • 控制反转
  • 所以,不要让你有一个像这样访问:

    Singleton.getInstance().doSometing();
    

    ...将你的“单例”定义为一个接口,并有其他的东西来管理它的生命周期,并将它注入你需要的地方,例如作为一个私有实例变量:

    @Inject private Singleton mySingleton;
    

    然后当你单元测试依赖于单例的类/组件/等时,你可以很容易地注入它的模拟版本。

    大多数依赖注入容器将允许您将组件标记为“单例”,但取决于容器来管理它。

    使用上述做法可以更容易地对代码进行单元测试,并让您专注于功能逻辑而不是布线逻辑。 这也意味着你的代码真正开始成为真正的面向对象,因为任何静态方法(包括构造函数)的使用都是有争议的过程。 因此,您的组件也开始变得真正可重用。

    查看Google Guice作为10的首发:

    http://code.google.com/p/google-guice/

    你也可以看看Spring和/或OSGi可以做这种事情。 那里有很多IOC / DI的东西。 :)


    根据定义,一个单例只有一个实例。 因此它的创作受到班级本身的严格控制。 通常它是一个具体的类,而不是一个接口,并且由于它的私有构造函数,它不是可分类的。 此外,它的客户端(通过调用Singleton.getInstance()或等价的)可以很容易地找到它,所以你不能轻易使用例如依赖注入来用模拟实例替换它的“真实”实例:

    class Singleton {
        private static final myInstance = new Singleton();
        public static Singleton getInstance () { return myInstance; }
        private Singleton() { ... }
        // public methods
    }
    
    class Client {
        public doSomething() {
            Singleton singleton = Singleton.getInstance();
            // use the singleton
        }
    }
    

    对于模拟,你最好需要一个可以自由分类的接口,并且通过依赖注入为其客户提供具体的实现。

    您可以放松Singleton实现,使其可以被测试

  • 提供一个可以由模拟子类以及“真实”子类实现的接口
  • 添加一个setInstance方法以允许在单元测试中替换实例
  • 例:

    interface Singleton {
        private static final myInstance;
        public static Singleton getInstance() { return myInstance; }
        public static void setInstance(Singleton newInstance) { myInstance = newInstance; }
        // public method declarations
    }
    
    // Used in production
    class RealSingleton implements Singleton {
        // public methods
    }
    
    // Used in unit tests
    class FakeSingleton implements Singleton {
        // public methods
    }
    
    class ClientTest {
        private Singleton testSingleton = new FakeSingleton();
        @Test
        public void test() {
            Singleton.setSingleton(testSingleton);
            client.doSomething();
            // ...
        }
    }
    

    正如你所看到的,你只能通过损害Singleton的“清洁度”来使你的使用Singleton的代码单元可测试。 最后,如果可以避免,最好不要使用它。

    更新:这里是Michael Feathers对遗留代码有效工作的强制性参考。

    链接地址: http://www.djcxy.com/p/13819.html

    上一篇: mocking a singleton class

    下一篇: Singleton class vs. class with static member