我有很多课程,我被要求在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()
或模拟实例吗?
答案 0 :(得分:24)
受到这样的线索的劝阻,我花了很长时间才注意到,单身人士并不难以嘲笑。毕竟我们为什么要使用c#?
只需使用Reflection。
使用提供的示例代码,您需要确保在将静态字段设置为模拟对象之前调用静态构造函数。否则它可能会覆盖您的模拟对象。在设置测试之前,只需在单例上调用任何无效的东西。
ISomeInterface unused = Singleton.Instance();
System.Reflection.FieldInfo instance = typeof(Example1).GetField("_instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
Mock<ISomeInterface> mockSingleton = new Mock<ISomeInterface>();
instance.SetValue(null, mockSingleton.Object);
我提供了使用Moq进行模拟的代码,但我猜Rhino Mocks非常相似。
答案 1 :(得分:11)
单身人士与可测试性不一致,因为他们很难改变。使用依赖注入将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或其他动态模拟库将Test Doubles的ISomeInterface注入到消费类中。
答案 2 :(得分:6)
这是一种使用委托的低触摸方法,可以在运行时初始设置并更改。通过示例更好地解释(具体来说,模拟DateTime.Now):
答案 3 :(得分:2)
图书中的示例:Working Effectively with Legacy Code
要在测试工具中运行包含单例的代码,我们必须放松单例属性。这是我们如何做到的。第一步是向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,我们可以创建一个a的测试实例 PermitRepository并设置它。我们想在我们的测试设置中编写这样的代码:
public void setUp() {
PermitRepository repository = new PermitRepository();
...
// add permits to the repository here
...
PermitRepository.setTestingInstance(repository);
}
答案 4 :(得分:1)
您可以模拟界面ISomeInterface。然后,重构使用它的代码以使用依赖注入来获取对单例对象的引用。我在代码中多次遇到过这个问题,我最喜欢这个解决方案。
例如:
public class UseTheSingleton
{
private ISomeInterface myX;
public UseTheSingleton(ISomeInterface x)
{
myX = x;
}
public void SomeMethod()
{
myX.
}
}
然后......
UseTheSingleton useIt = UseTheSingleton(Example1.Instance);
答案 5 :(得分:1)
你已经开始了这个,但是对于难以测试的类(静态等等),你可以使用adapter
设计模式来编写一个包装这个难以测试代码的包装器。使用此适配器的interface
,您可以单独测试代码。
对于任何单元测试建议和进一步的测试问题,请查看Google Testing Blog,特别是Misko的文章。
你说你正在编写测试,所以可能为时已晚,但是你能否将静态重构为实例?或者是否有一个真正的理由说为什么所说的课程应该保持静止?
答案 6 :(得分:0)
您不必立即修复所有用途,只需要处理您现在正在处理的用途。将ISomeInterface字段添加到测试的类中,并通过构造函数设置它。如果你正在使用Resharper(你 使用Resharper,不是吗?),大部分内容都是微不足道的。如果这非常繁琐,你可以有多个构造函数,一个用于设置新的依赖项字段,另一个用单例作为默认值调用第一个构造函数。