单例和单元测试

2022-08-31 20:12:20

Effective Java在单元测试单例上有以下声明

使类成为单例可能会使测试其客户端变得困难,因为除非它实现作为其类型的接口,否则不可能用模拟实现代替单例。

谁能解释为什么会这样?


答案 1

您可以使用反射来重置单例对象,以防止测试相互影响。

@Before
public void resetSingleton() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
   Field instance = MySingleton.class.getDeclaredField("instance");
   instance.setAccessible(true);
   instance.set(null, null);
}

参考:单元测试单例


答案 2

问题不在于测试单例本身;这本书说,如果你试图测试的一个类依赖于一个单例,那么你很可能会遇到问题。

除非您 (1) 使单例实现一个接口,并且 (2) 使用该接口将单例注入到您的类中。

例如,单例通常直接实例化,如下所示:

public class MyClass
{
    private MySingleton __s = MySingleton.getInstance() ;

    ...
}

MyClass现在可能很难自动测试。例如,正如@Boris Pavlović 在他的回答中指出的那样,如果单例的行为基于系统时间,那么您的测试现在也依赖于系统时间,并且您可能无法测试依赖于星期几的用例。

但是,如果您的单例“实现了作为其类型的接口”,那么您仍然可以使用该接口的单例实现,只要您将其传入:

public class SomeSingleton
    implements SomeInterface
{
    ...
}

public class MyClass
{
    private SomeInterface __s ;

    public MyClass( SomeInterface s )
    {
        __s = s ;
    }

    ...
}

...

MyClass m = new MyClass( SomeSingleton.getInstance() ) ;

从测试的角度来看,你现在并不关心是否是单例:你也可以传递你想要的任何其他实现,包括单例实现,但最有可能的是,你会使用某种你从测试中控制的模拟。MyClassSomeSingleton

顺便说一句,这不是这样做的方法:

public class MyClass
{
    private SomeInterface __s = SomeSingleton.getInstance() ;

    public MyClass()
    {
    }

    ...
}

这在运行时仍然相同,但是对于测试,您现在再次依赖于 。SomeSingleton


推荐