use*_*434 38 java singleton unit-testing
Effective Java在单元测试单例上有以下声明
使类成为单例可能会使测试其客户端变得困难,因为除非它实现了作为其类型的接口,否则不可能将模拟实现替换为单例.
任何人都可以解释为什么会这样吗?
小智 39
您可以使用反射来重置单个对象,以防止测试相互影响.
@Before
public void resetSingleton() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field instance = MySingleton.class.getDeclaredField("instance");
instance.setAccessible(true);
instance.set(null, null);
}
Run Code Online (Sandbox Code Playgroud)
参考:单元测试 - 单例
模拟需要接口,因为您正在做的是用模仿测试所需内容的模板取代真实的基础行为.由于客户端只处理接口引用类型,因此不需要知道实现是什么.
您不能在没有接口的情况下模拟具体类,因为如果没有测试客户端知道它,您就无法替换该行为.在这种情况下,这是一个全新的课程.
所有类别都是如此,Singleton与否.
我认为这实际上取决于单例访问模式的实现.
例如
MySingleton.getInstance()
Run Code Online (Sandbox Code Playgroud)
可能非常难以测试
MySingletonFactory mySingletonFactory = ...
mySingletonFactory.getInstance() //this returns a MySingleton instance or even a subclass
Run Code Online (Sandbox Code Playgroud)
不提供有关使用单例的事实的任何信息.所以你可以自由更换你的工厂.
注意:单例是通过在应用程序中只是该类的一个实例来定义的,但是获取或存储它的方式不必通过静态方式.
这很简单哦.
在单元测试中,您希望隔离您的SUT(您正在测试的类).你不想来测试一堆类,因为这会破坏的目的单元 - 测试.
但并非所有班级都能自己做所有事情,对吧?大多数类使用其他类来完成他们的工作,并且它们在其他类之间进行调解,并添加一些自己的类,以获得最终结果.
关键是 - 你不关心你的SUT课程如何依赖于工作.您关心SUT如何与这些类一起使用.这就是你存根或模拟你的SUT需要的类的原因.您可以使用这些模拟,因为您可以将它们作为SUT的构造函数参数传递.
对于单身人士 - 糟糕的是该getInstance()方法是全球可访问的.这意味着您通常在类中调用它,而不是依赖于稍后可以模拟的接口.这就是为什么当你想测试你的SUT时不可能更换它.
解决方案不是使用偷偷摸摸的public static MySingleton getInstance()方法,而是依赖于您的类需要使用的接口.这样做,你可以随时传递测试双打.
问题不在于测试单身人士自己; 这本书说,如果你试图测试的课程取决于单身,那么你可能会有问题.
除非,即,您(1)使单例实现一个接口,并(2)使用该接口将单例注入您的类.
例如,单例通常直接实例化,如下所示:
public class MyClass
{
private MySingleton __s = MySingleton.getInstance() ;
...
}
Run Code Online (Sandbox Code Playgroud)
MyClass现在可能很难自动测试.例如,正如@BorisPavlović在他的回答中指出的那样,如果单例的行为是基于系统时间的,那么你的测试现在也取决于系统时间,你可能无法测试依赖于系统时间的情况.一周中的天.
但是,如果你的单例"实现了一个作为其类型的接口",那么你仍然可以使用该接口的单例实现,只要你传入它:
public class SomeSingleton
implements SomeInterface
{
...
}
public class MyClass
{
private SomeInterface __s ;
public MyClass( SomeInterface s )
{
__s = s ;
}
...
}
...
MyClass m = new MyClass( SomeSingleton.getInstance() ) ;
Run Code Online (Sandbox Code Playgroud)
从测试的角度来看,MyClass你现在不关心是否SomeSingleton是单例:你也可以传入你想要的任何其他实现,包括单例实现,但很可能你会使用你控制的某种类型的模拟试验.
顺便说一句,这不是这样做的方式:
public class MyClass
{
private SomeInterface __s = SomeSingleton.getInstance() ;
public MyClass()
{
}
...
}
Run Code Online (Sandbox Code Playgroud)
这在运行时仍然有效,但是对于测试,你现在再次依赖SomeSingleton.