单身和单元测试

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)

参考:单元测试 - 单例


duf*_*ymo 9

模拟需要接口,因为您正在做的是用模仿测试所需内容的模板取代真实的基础行为.由于客户端只处理接口引用类型,因此不需要知道实现是什么.

您不能在没有接口的情况下模拟具体类,因为如果没有测试客户端知道它,您就无法替换该行为.在这种情况下,这是一个全新的课程.

所有类别都是如此,Singleton与否.

  • 说得好!“ Mockit”可以模拟具体的类,但是它确实进行了非常邪恶的自省,这些行为可能具有非常奇怪的行为。单例(用Java实现)是邪恶的!Guice做得更好... (2认同)

Liv*_* T. 7

我认为这实际上取决于单例访问模式的实现.

例如

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)

不提供有关使用单例的事实的任何信息.所以你可以自由更换你的工厂.

注意:单例是通过在应用程序中只是该类的一个实例来定义的,但是获取或存储它的方式不必通过静态方式.


Yam*_*vic 7

这很简单哦.

在单元测试中,您希望隔离您的SUT(您正在测试的类).你不想来测试一堆类,因为这会破坏的目的单元 - 测试.

但并非所有班级都能自己做所有事情,对吧?大多数类使用其他类来完成他们的工作,并且它们在其他类之间进行调解,并添加一些自己的类,以获得最终结果.

关键是 - 你不关心你的SUT课程如何依赖于工作.您关心SUT如何与这些类一起使用.这就是你存根或模拟你的SUT需要的类的原因.您可以使用这些模拟,因为您可以将它们作为SUT的构造函数参数传递.

对于单身人士 - 糟糕的是该getInstance()方法是全球可访问的.这意味着您通常类中调用它,而不是依赖于稍后可以模拟的接口.这就是为什么当你想测试你的SUT时不可能更换它.

解决方案不是使用偷偷摸摸的public static MySingleton getInstance()方法,而是依赖于您的类需要使用的接口.这样做,你可以随时传递测试双打.


Rod*_*zel 7

问题不在于测试单身人士自己; 这本书说,如果你试图测试的课程取决于单身,那么你可能会有问题.

除非,即,您(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.