如何模拟静态单例?

我有一些课程,我被要求添加一些unit testing与Rhino Mocks并有一些问题。

首先,我知道RhinoMocks不允许嘲笑静态成员。 我正在寻找我的选择(除了使用TypeMock)。

我所拥有的类的示例与以下类似:

class Example1 : ISomeInterface { private static ISomeInterface _instance; private Example1() { // set properties via private static methods } static Example1() { _instance = new Example1(); } public static ISomeInterface Instance() { get { return _instance; } } // Instance properties // Other Instance Properties that represent objects that follow a similar pattern. } 

所以当我打电话给上面的课时,它看起来像这样……

 Example1.Instance.SomeObject.GoDownARabbitHole(); 

有没有办法在这种情况下模拟SomeObject.GoDownARabbitHole()或模拟实例?

单身人士与可测试性不一致,因为他们很难改变。 使用dependency injection将ISomeInterface实例注入到消费类中会更好:

 public class MyClass { private readonly ISomeInterface dependency; public MyClass(ISomeInterface dependency) { if(dependency == null) { throw new ArgumentNullException("dependency"); } this.dependency = dependency; } // use this.dependency in other members } 

请注意Guard Claus与readonly关键字一起如何保证ISomeInterface实例始终可用。

这将允许您使用Rhino Mocks或其他动态模拟库将ISomeInterface的Test Doubles注入到消费类中。

由于这样的线索而气馁,我花了很长时间才注意到,单身人士并不难以嘲笑。 毕竟我们为什么要使用c#?

只需使用Reflection。

使用提供的示例代码,您需要确保在将静态字段设置为模拟对象之前调用静态构造函数。 否则它可能会覆盖您的模拟对象。 在设置测试之前,只需在单例上调用任何无效的东西。

 ISomeInterface unused = Singleton.Instance(); System.Reflection.FieldInfo instance = typeof(Example1).GetField("_instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); Mock mockSingleton = new Mock(); instance.SetValue(null, mockSingleton.Object); 

我提供了使用Moq进行模拟的代码,但我猜Rhino Mocks非常相似。

这是一种使用委托的低触摸方法,可以在最初设置并在运行时更改。 通过示例更好地解释(具体来说,模拟DateTime.Now):

http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/11/09/systemtime-versus-isystemclock-dependencies-revisited.aspx

书中的示例 : 有效地使用旧版代码

要在测试工具中运行包含单例的代码,我们必须放松单例属性。 这是我们如何做到的。 第一步是向singleton类添加一个新的静态方法。 该方法允许我们替换单例中的静态实例。 我们称之为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) { ... } ... } 

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

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

您可以模拟接口ISomeInterface。 然后,重构使用它的代码以使用dependency injection来获取对单例对象的引用。 我在代码中多次遇到过这个问题,我最喜欢这个解决方案。

例如:

 public class UseTheSingleton { private ISomeInterface myX; public UseTheSingleton(ISomeInterface x) { myX = x; } public void SomeMethod() { myX. } } 

然后 …

 UseTheSingleton useIt = UseTheSingleton(Example1.Instance); 

查看dependency injection 。

你已经开始这个了,但是对于难以测试的类(静态等等)你可以使用adapter设计模式来编写一个包装这个难以测试的代码。 使用此适配器的interface ,您可以单独测试代码。

对于任何unit testing建议和进一步测试问题,请查看Google测试博客 ,特别是Misko的文章。

你说你正在编写测试,所以可能为时已晚,但是你可以将静态重构为实例吗? 或者,为什么所说的课程应该保持静态?

您不必立即修复所有用途,只需要处理您正在处理的用途。 将ISomeInterface字段添加到测试的类中,并通过构造函数设置它。 如果你正在使用Resharper(你使用的是Resharper,不是吗?),大部分内容都是微不足道的。 如果这非常繁琐,您可以拥有多个构造函数,一个用于设置新的依赖项字段,另一个用单例作为默认值调用第一个构造函数。