嘲笑一个单身class

我最近读到,单独做一个class级单元使得不可能嘲笑class级的对象,这使得很难testing它的客户。 我无法立即明白其根本原因。 有人可以解释一下是什么让模拟单身课程变得不可能吗? 另外,还有更多的问题与创build一个类的单身人士?

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

所以,我们不要讨论单身人士的好坏(这里还有另一个问题 ),但是让我们看看在testing过程中如何处理它们。 首先,我们来看看单例的一个通用实现:

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"; } } 

这里有两个testing问题:

  1. 构造函数是私有的,所以我们不能扩展它(并且我们不能控制testing中实例的创build,但是,这就是单例)。

  2. getInstance是静态的,因此在使用单例的代码中很难注入一个伪造的而不是单例对象。

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

例如,模拟静态方法调用显示了如何使用JMockit Expectations来模拟单例。

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

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

  • 编程到接口
  • dependency injection
  • 控制倒置

所以,而不是像你这样访问一个人:

 Singleton.getInstance().doSometing(); 

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

 @Inject private Singleton mySingleton; 

然后当你unit testing依赖于单例的类/组件/等,你可以很容易地注入一个模拟版本。

大多数dependency injection容器将允许您将组件标记为“单例”,但是由容器来pipe理。

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

查看谷歌Guice作为启动10:

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

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

一个单身人士,根据定义,只有一个实例。 因此它的创作受到阶级本身的严格控制。 通常它是一个具体的类,而不是一个接口,由于它的私有构造函数,它是不可分类的。 而且,它的客户端(通过调用Singleton.getInstance()或者等价的)可以很方便的find它,所以你不能轻易的使用dependency injection ( Dependency Injection)来用模拟实例replace它的“真实”实例。

 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 } } 

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

你可以放松Singleton实现,使其可以被testing

  • 提供一个可以通过模拟子类以及“真实”子类来实现的接口
  • 添加一个setInstance方法来允许在unit testing中replace实例

例:

 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的代码单元可以被testing。 最后,如果可以避免的话,最好不要使用它。

更新:这里是必须参考的工作有效地与遗产代码由迈克尔羽毛。

它非常依赖于单例实现。 但主要是因为它有一个私人的构造函数,因此你不能扩展它。 但是你有以下select

  • 做一个接口 – SingletonInterface
  • 让你的单例类实现这个接口
  • Singleton.getInstance()返回SingletonInterface
  • 在你的testing中提供一个SingletonInterface的模拟实现
  • 使用reflection将其设置在Singletonprivate static字段中。

但是你最好避免单身人士(代表一个全球性的国家)。 本讲座从可testing性的angular度解释了一些重要的devise概念。

这并不是说辛格尔顿模式本身就是纯粹的邪恶,但即使是在不合格的情况下,它也被大量使用。 许多开发人员认为“哦,我可能只需要其中的一个,所以让我们做一个单身人士”。 事实上,你应该想:“我可能只需要其中的一个,所以我们在我的程序开始时就构build一个,并在需要时传递引用”。

单身和testing的第一个问题不是因为单身而是因为懒惰。 由于获得单例的方便,对单例对象的依赖通常直接embedded到方法中,使得单例更改为具有相同接口但具有不同实现的另一对象(例如,模仿对象)。

代替:

 void foo() { Bar bar = Bar.getInstance(); // etc... } 

喜欢:

 void foo(IBar bar) { // etc... } 

现在你可以用一个你可以控制的模拟bar对象来testing函数foo 。 您已经删除了依赖关系,以便您可以在不testingbar情况下testingfoo

单身人士和testing的另一个问题是testing单身人士本身。 一个单例是(devise上)非常难以重构,所以例如你只能testing一次单例构造函数。 Bar的单个实例也可能在testing之间保持状态,这取决于运行testing的顺序而导致成功或失败。

有一种方法来模拟辛格尔顿。 使用powermock来嘲笑静态方法,并使用YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);来调用构造函数YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper); YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);

发生什么事是Singleton字节码在运行时正在改变。

请享用