Java 模拟单例类
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2302179/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
mocking a singleton class
提问by Aadith Ramia
I recently read that making a class singleton makes it impossible to mock the objects of the class, which makes it difficult to test its clients. I could not immediately understand the underlying reason. Can someone please explain what makes it impossible to mock a singleton class? Also, are there any more problems associated with making a class singleton?
我最近读到,使类单例无法模拟该类的对象,这使得测试其客户端变得困难。我无法立即理解根本原因。有人可以解释一下是什么让模拟单例类变得不可能吗?另外,制作类单例还有其他问题吗?
采纳答案by Pascal Thivent
Of course, I could write something like don't use singleton, they are evil, use Guice/Spring/whateverbut first, this wouldn't answer your question and second, you sometimes have todeal with singleton, when using legacy code for example.
当然,我可以写一些东西,比如不要使用单例,它们是邪恶的,使用 Guice/Spring/无论如何,但首先,这不会回答你的问题,其次,当使用遗留代码时,你有时必须处理单例例子。
So, let's not discuss the good or bad about singleton (there is another questionfor this) but let's see how to handle them during testing. First, let's look at a common implementation of the 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";
}
}
There are two testing problems here:
这里有两个测试问题:
The constructor is private so we we can't extend it (and we can't control the creation of instances in tests but, well, that's the point of singletons).
The
getInstance
is static so it's hard to inject a fake instead of the singleton object in the code using the singleton.
构造函数是私有的,因此我们无法扩展它(并且我们无法控制测试中实例的创建,但这就是单例的重点)。
该
getInstance
是静态的,所以很难注入假的,而不是单独的对象使用Singleton的代码。
For mocking frameworks based on inheritance and polymorphism, both points are obviously big issues. If you have the control of the code, one option is to make your singleton "more testable" by adding a setter allowing to tweak the internal field as described in Learn to Stop Worrying and Love the Singleton(you don't even need a mocking framework in that case). If you don't, modernmocking frameworks based on interception and AOP concepts allow to overcome the previously mentioned problems.
对于基于继承和多态的mocking框架来说,这两点显然都是大问题。如果您可以控制代码,一种选择是通过添加允许调整内部字段的 setter 使您的单身人士“更可测试”,如学习停止担心和热爱单身人士(您甚至不需要嘲笑在这种情况下的框架)。如果不这样做,基于拦截和 AOP 概念的现代模拟框架可以克服前面提到的问题。
For example, Mocking Static Method Callsshows how to mock a Singleton using JMockit Expectations.
例如,模拟静态方法调用展示了如何使用JMockit Expectations模拟单例。
Another option would be to use PowerMock, an extension to Mockito or JMock which allows to mock stuff normally not mockable like static, final, private or constructor methods. Also you can access the internals of a class.
另一种选择是使用PowerMock,它是 Mockito 或 JMock 的扩展,它允许模拟通常不可模拟的东西,如静态、最终、私有或构造函数方法。您也可以访问类的内部结构。
回答by Bozho
It very much depends on the singleton implementation. But it mostly because it has a private constructor and hence you can't extend it. But you have the following option
这在很大程度上取决于单例实现。但这主要是因为它有一个私有构造函数,因此你不能扩展它。但你有以下选择
- make an interface -
SingletonInterface
- make your singleton class implement that interface
- let
Singleton.getInstance()
returnSingletonInterface
- provide a mock implementation of
SingletonInterface
in your tests - set it in the
private static
field onSingleton
using reflection.
- 做一个界面——
SingletonInterface
- 让您的单例类实现该接口
- 让
Singleton.getInstance()
回SingletonInterface
SingletonInterface
在您的测试中提供一个模拟实现private static
在Singleton
使用反射的字段中设置它。
But you'd better avoid singletons (which represent a global state). This lectureexplains some important design concepts from testability point of view.
但是你最好避免单例(代表全局状态)。本讲座从可测试性的角度解释了一些重要的设计概念。
回答by Péter T?r?k
A Singleton, by definition, has exactly one instance. Hence its creation is strictly controlled by the class itself. Typically it is a concrete class, not an interface, and due to its private constructor it is not subclassable. Moreover, it is found actively by its clients (by calling Singleton.getInstance()
or an equivalent), so you can't easily use e.g. Dependency Injectionto replace its "real" instance with a mock instance:
根据定义,单例只有一个实例。因此它的创建是由类本身严格控制的。通常它是一个具体的类,而不是一个接口,并且由于它的私有构造函数,它不能被子类化。此外,它的客户端会主动找到它(通过调用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
}
}
For mocks, you would ideally need an interface which can be freely subclassed, and whose concrete implementation is provided to its client(s) by dependency injection.
对于模拟,理想情况下,您需要一个可以自由子类化的接口,并且其具体实现通过依赖注入提供给其客户端。
You can relax the Singleton implementation to make it testable by
您可以放宽单例实现以使其可测试
- providing an interface which can be implemented by a mock subclass as well as the "real" one
- adding a
setInstance
method to allow replacing the instance in unit tests
- 提供一个可以由模拟子类和“真实”子类实现的接口
- 添加
setInstance
允许在单元测试中替换实例的方法
Example:
例子:
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();
// ...
}
}
As you see, you can only make your Singleton-using code unit testable by compromising the "cleanness" of the Singleton. In the end, it is best not to use it at all if you can avoid it.
如您所见,您只能通过损害 Singleton 的“清洁度”来使使用 Singleton 的代码单元可测试。最后,如果可以避免,最好完全不要使用它。
Update:And here is the obligatory reference to Working Effectively With Legacy Codeby Michael Feathers.
更新:这里是Michael Feathers 对使用遗留代码有效工作的强制性参考。
回答by Mark Byers
It's not that the Singleton pattern is itself pure evil, but that is massively overused even in situations where it is inapproriate. Many developers think "Oh, I'll probably only ever need one of these so let's make it a singleton". In fact you should be thinking "I'll probably only ever need one of these, so let's construct one at the start of my program and pass references where it is needed."
并不是说单例模式本身就是纯粹的邪恶,而是即使在不适当的情况下也被大量过度使用。许多开发人员认为“哦,我可能只需要其中一个,所以让我们将其设为单例”。事实上,您应该考虑“我可能只需要其中之一,所以让我们在程序开始时构建一个,并在需要的地方传递引用。”
The first problem with singleton and testing is not so much because of the singleton but due to laziness. Because of the convenience of getting a singleton, the dependency on the singleton object is often embedded directly into the methods which makes it very difficult to change the singleton to another object with the same interface but with a different implementation (for example, a mock object).
单身和测试的第一个问题与其说是因为单身,不如说是因为懒惰。由于获取单例的便利性,对单例对象的依赖通常直接嵌入到方法中,这使得将单例更改为具有相同接口但具有不同实现的另一个对象(例如,模拟对象)变得非常困难)。
Instead of:
代替:
void foo() {
Bar bar = Bar.getInstance();
// etc...
}
prefer:
更喜欢:
void foo(IBar bar) {
// etc...
}
Now you can test function foo
with a mocked bar
object which you can control. You've removed the dependency so that you can test foo
without testing bar
.
现在您可以foo
使用bar
您可以控制的模拟对象测试功能。您已删除依赖项,以便foo
无需测试即可进行测试bar
。
The other problem with singletons and testing is when testing the singleton itself. A singleton is (by design) very difficult to reconstruct, so for example you can only test the singleton contructor once. It's also possible that the single instance of Bar
retains state between tests, causing success or failure depending on the order that the tests are run.
单例和测试的另一个问题是在测试单例本身时。单例(按设计)很难重建,因此例如您只能测试单例构造函数一次。单个实例也可能Bar
在测试之间保留状态,导致成功或失败,具体取决于测试运行的顺序。
回答by brindy
The best way to mock a singleton is not to use them at all, or at least not in the traditional sense. A few practices you might want to look up are:
模拟单身人士的最好方法是根本不使用它们,或者至少不要在传统意义上使用它们。您可能想要查找的一些实践是:
- programming to interfaces
- dependency injection
- inversion of control
- 编程接口
- 依赖注入
- 控制反转
So rather than having a single you access like this:
因此,您不必像这样访问单个:
Singleton.getInstance().doSometing();
... define your "singleton" as an interface and have something else manage it's lifecycle and inject it where you need it, for instance as a private instance variable:
...将您的“单例”定义为一个接口,并使用其他东西管理它的生命周期并将其注入您需要的地方,例如作为私有实例变量:
@Inject private Singleton mySingleton;
Then when you are unit testing the class/components/etc which depend on the singleton you can easily inject a mock version of it.
然后,当您对依赖于单例的类/组件/等进行单元测试时,您可以轻松注入它的模拟版本。
Most dependency injection containers will let you mark up a component as 'singleton', but it's up to the container to manage that.
大多数依赖注入容器都可以让您将组件标记为“单例”,但这取决于容器来管理。
Using the above practices makes it much easier to unit test your code and lets you focus on your functional logic instead of wiring logic. It also means your code really starts to become truly Object Oriented, as any use of static methods (including constructors) is debatably procedural. Thus your components start to also become truly reusable.
使用上述实践可以更轻松地对代码进行单元测试,并让您专注于功能逻辑而不是接线逻辑。这也意味着您的代码真正开始成为真正的面向对象,因为任何静态方法(包括构造函数)的使用都是有争议的过程。因此,您的组件也开始变得真正可重用。
Check out Google Guice as a starter for 10:
查看 Google Guice 作为 10 的初学者:
http://code.google.com/p/google-guice/
http://code.google.com/p/google-guice/
You could also look at Spring and/or OSGi which can do this kind of thing. There's plenty of IOC / DI stuff out there. :)
您还可以查看可以执行此类操作的 Spring 和/或 OSGi。那里有很多 IOC / DI 的东西。:)
回答by Moshe Tsabari
There is a way to mock Singleton. Use powermock to mock static method and use Whitebox to invoke constructor YourClass mockHelper = Whitebox
.invokeConstructor(YourClass.class);
Whitebox.setInternalState(mockHelper, "yourdata",mockedData);
PowerMockito.mockStatic(YourClass.class);
Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);
有一种方法可以模拟 Singleton。使用powermock模拟静态方法,使用Whitebox调用构造函数YourClass mockHelper = Whitebox
.invokeConstructor(YourClass.class);
Whitebox.setInternalState(mockHelper, "yourdata",mockedData);
PowerMockito.mockStatic(YourClass.class);
Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);
What is happening is that the Singleton byte code is changing in run-time .
发生的事情是 Singleton 字节码在运行时发生变化。
enjoy
请享用