【问题标题】:Unit testing with singletons单例单元测试
【发布时间】:2010-01-18 12:27:06
【问题描述】:

我已经使用 Visual Studio Team Edition 测试框架准备了一些自动测试。我希望其中一项测试按照程序中的正常方式连接到数据库:

string r_providerName = ConfigurationManager.ConnectionStrings["main_db"].ProviderName;

但我在这一行收到异常。我想这是因为 ConfigurationManager 是一个单例。如何解决单元测试的单例问题?


感谢您的回复。他们都非常有启发性。

【问题讨论】:

  • 确切的错误信息是什么?

标签: unit-testing singleton


【解决方案1】:

【讨论】:

  • 所有这些参考资料都更深入地解决了这个问题,我可以总结为一个答案。他们绝对很棒。
  • +1!另见 Michael Feathers 的《有效地使用遗留代码》一书;他提供了使用单例进行测试的技术。 amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/…
  • 特别有趣的是 Performant Singletons 链接,导致 403 Forbidden 错误页面。表达拒绝的一种非常微妙的方式。
  • 添加进一步的想法............单元测试可以/可以并行运行(想想“构建服务器”)。虽然全局变量/单例可能是线程安全的,但它不是线程独立的。因此,并行运行的测试并设置/取消设置全局变量,每个测试都可以搞砸其他测试。但感谢您巩固反对全局变量方法的论点。
  • 在某些情况下您无法移除signleton,但您仍需要对其进行测试。所以你的答案不完整
【解决方案2】:

您可以使用构造函数依赖注入。示例:

public class SingletonDependedClass
{
    private string _ProviderName;

    public SingletonDependedClass()
        : this(ConfigurationManager.ConnectionStrings["main_db"].ProviderName)
    {
    }

    public SingletonDependedClass(string providerName)
    {
        _ProviderName = providerName;
    }
}

这允许您在测试期间将连接字符串直接传递给对象。

此外,如果您使用 Visual Studio Team Edition 测试框架,您可以将带有参数的构造函数设为私有,并通过访问器测试类。

实际上,我通过模拟解决了这类问题。示例:

你有一个依赖单例的类:

public class Singleton
{
    public virtual string SomeProperty { get; set; }

    private static Singleton _Instance;
    public static Singleton Insatnce
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = new Singleton();
            }

            return _Instance;
        }
    }

    protected Singleton()
    {
    }
}

public class SingletonDependedClass
{
    public void SomeMethod()
    {
        ...
        string str = Singleton.Insatnce.SomeProperty;
        ...
    }
}

首先SingletonDependedClass需要重构为Singleton实例作为构造函数参数:

public class SingletonDependedClass
{    
    private Singleton _SingletonInstance;

    public SingletonDependedClass()
        : this(Singleton.Insatnce)
    {
    }

    private SingletonDependedClass(Singleton singletonInstance)
    {
        _SingletonInstance = singletonInstance;
    }

    public void SomeMethod()
    {
        string str = _SingletonInstance.SomeProperty;
    }
}

测试SingletonDependedClass(使用Moq mocking library):

[TestMethod()]
public void SomeMethodTest()
{
    var singletonMock = new Mock<Singleton>();
    singletonMock.Setup(s => s.SomeProperty).Returns("some test data");
    var target = new SingletonDependedClass_Accessor(singletonMock.Object);
    ...
}

【讨论】:

    【解决方案3】:

    书中的例子Working Effectively with Legacy Code

    这里也给出了相同的答案: https://stackoverflow.com/a/28613595/929902

    要在测试工具中运行包含单例的代码,我们必须放宽单例属性。这是我们如何做到的。第一步是向单例类添加一个新的静态方法。该方法允许我们替换单例中的静态实例。我们称之为 setTestingInstance

    public class PermitRepository
    {
        private static PermitRepository instance = null;
        private PermitRepository() {}
        public static void setTestingInstance(PermitRepository newInstance)
        {
            instance = newInstance;
        }
        public static PermitRepository getInstance()
        {
            if (instance == null) {
                instance = new PermitRepository();
            }
            return instance;
        }
        public Permit findAssociatedPermit(PermitNotice notice) {
        ...
        }
        ...
    }
    

    现在我们有了这个设置器,我们可以创建一个测试实例 PermitRepository 并设置它。我们想在我们的测试设置中编写这样的代码:

    public void setUp() {
        PermitRepository repository = PermitRepository.getInstance();
        ...
        // add permits to the repository here
        ...
        PermitRepository.setTestingInstance(repository);
    }
    

    【讨论】:

    • setUp() 方法中似乎缺少某些内容。 PermitRepository 有一个私有构造函数,所以你不能在那里使用 new ...
    • 在您的设置方法中,您在设置测试实例之前调用 get instance。你想先设置你的测试实例。
    【解决方案4】:

    您在这里面临一个更普遍的问题。如果滥用,单例会阻碍可测试性。

    我在解耦设计的上下文中完成了这个问题的detailed analysis。我会试着总结一下我的观点:

    1. 如果您的 Singleton 带有重要的全局状态,请不要使用 Singleton。这包括持久性存储,例如数据库、文件等。
    2. 如果类名对单例对象的依赖不明显,则应注入依赖项。将单例实例注入类的需要证明了该模式的错误使用(参见第 1 点)。
    3. 假定单例的生命周期与应用程序的生命周期相同。大多数 Singleton 实现都使用延迟加载机制来实例化自己。这是微不足道的,它们的生命周期不太可能改变,否则你不应该使用 Singleton。

    【讨论】:

      猜你喜欢
      • 2015-02-09
      • 2023-03-27
      • 2012-01-08
      • 1970-01-01
      • 1970-01-01
      • 2019-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多