如何模拟静态单例?
我有一些课程,我被要求添加一些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):
书中的示例 : 有效地使用旧版代码
要在测试工具中运行包含单例的代码,我们必须放松单例属性。 这是我们如何做到的。 第一步是向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,不是吗?),大部分内容都是微不足道的。 如果这非常繁琐,您可以拥有多个构造函数,一个用于设置新的依赖项字段,另一个用单例作为默认值调用第一个构造函数。