单元测试单例模式

14

我有一个包含统计信息对象引用的单例。

当我在使用该单例的程序上运行一些单元测试时,这些值会在测试之间保留。

我认为当我执行Program.Main()时,在单元测试之间都会重新开始,但不知何故它记住了上次测试的结果。

我如何编写能够相互隔离的单元测试(我不想使用clean()函数——我希望它能够以新的"一切"重新开始)?


1
请添加一些代码示例 - Sergey K
8
这就是单例模式的问题之一 :) 你可以重置单例(通常通过使用反射将 _instance 指针设置为 null),或者你需要考虑将其加载到自己的应用程序域中进行测试... “单例模式”的一个问题在于,它可能会影响代码的可测试性。如果你想要重置单例对象的状态以进行测试,一种方法是使用反射将 _instance 指针设置为 null。另一种方法是考虑将单例对象加载到自己的应用程序域中进行测试。 - forsvarir
1
@forsvarir:这是正确的答案。为什么要发表评论呢? - razlebe
3
@razelbe:我认为这不是一个答案,而是一个观察和指向正确方向的指针。一个真正的答案应该更加实质性一些 :0) - forsvarir
@razelbe:没错,但如果你让4个朋友给它点赞,我就能获得十分之一的专家徽章。 - forsvarir
显示剩余2条评论
5个回答

25

简洁版:不要将单例写成单例。将它们编写为普通类,并通过控制反转容器调用它们,其中您已将该类配置为单例。

这样,您可以完全进行单元测试,如果您决定今天或明天单例不是该类的正确生命周期,则只需修改IoC容器的配置即可。


1
我会检查这个控制反转容器 - 尽管我没有时间投入其中。最终,仍然存在单例模式,我将验证这种设计模式如何解决这个问题。谢谢! - Eyal

5
我在这里写了一篇关于此的文章:http://pvlerick.github.io/2017/03/how-to-get-rid-of-a-singleton 简而言之: 1. 从Singleton中提取一个接口(即使您不拥有它),并使您的类针对该接口而不是Singleton实例工作; 2. 根据您是否拥有Singleton,您可以使其实现该接口,或者您需要一个简单的适配器。

3
为每个测试创建一个新的AppDomain很困难。例如,NUnit会为每个测试会话(通过所有选择的测试的一次遍历)创建一个新的AppDomain。然后,NUnit必须将其代码注入该AppDomain以运行测试。这不是易事。您不能只是创建一个新的AppDomain并期望在其中自动运行。您必须让您的代码在其中运行。总体而言,此答案可能有可能性,但我不同意“这不应该太难”的部分。 - Mike Two

2

当试图测试单例本身时,以下可能是一种解决方案:

public class Singleton
{
    private static Singleton _Instance;

    public static Singleton getInstance() {
        if (_Instance == null)
        {
            _Instance = new Singleton();
        }

        return _Instance;
    }

    private Singleton()
    {
    }

    public static resetForTesting() {
        _Instance = null
    }
}

因此,在您的单元测试框架中,您将在每个单元测试之前调用Singleton.resetForTesting()

注意:这种方法的缺点是,没有代码级别的限制可以阻止某人在生产代码中调用此方法,即使它只应该与测试代码一起使用。因此,您必须依靠文档来传达这一点给其他人。


1
你可以通过将resetForTesting方法设为internal,然后使该程序集内部可见来添加限制,这样就能包含测试类。请参阅Microsoft文档 - devklick

1

你所链接的问题是关于 OP 遇到的异常,但是那个问题的被接受的答案包含了很多与此问题相关的有用链接。你可能需要更新你的超链接。 - razlebe
2
确实。我正在努力帮助你。 :) - razlebe

0

你可以为单例类添加属性设置器,以重新分配单例实例。这样,在测试中,你就可以对单例进行存根/模拟。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接