如果想了解mocking的一般背景,可以参考这篇答案,它很好地解释了mocking以及为什么你可能想在单元测试中使用它。
具体来说,Moq是一个库,可以让你轻松创建模拟对象并控制它们的行为。最好通过示例来描述,所以让我们看看你的第一个代码示例:
public interface IFoo {
public bool DoSomething(string);
public bool TryParse(string, out string));
}
var mock = new Mock<IFoo>();
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);
第1行创建了一个IFoo
接口的模拟实现(mock implementation)。在后台,Moq使用Castle DynamicProxy库动态创建IFoo
的具体实现,然后将其包装在自己的Mock
类中,以便我们可以配置它的行为。
现在我们有了一个模拟对象,我们经常需要配置它如何响应其方法的调用,这样我们就可以测试我们的被测系统对其的反应。第2行的Setup
方法完成了这个任务,告诉我们的模拟对象当使用参数等于"ping"
调用DoSomething
时返回true
。想象一下您正在使用此模拟对象来测试以下类:
public class TestObject
{
public string TestMethod(IFoo foo)
{
if (foo.DoSomething("ping"))
return "ping";
else if (foo.DoSomething("pong"))
return "pong";
return "blah";
}
}
为了获得完整的测试覆盖率,您需要实现一个
IFoo
接口,它可以:
- 在一个测试中返回
true
,以响应"ping"
- 在另一个测试中返回
true
,以响应"pong"
- 在最后一个测试中,对于任何参数都返回
false
您可以创建自己的模拟对象来实现这种行为,例如:
public class MockFoo : IFoo
{
string trueValue;
public MockFoo(string trueValue)
{
this.trueValue = trueValue;
}
public bool DoSomething(string value)
{
return value == trueValue;
}
}
但是当你有复杂的逻辑、多个参数或者许多依赖关系时,这种方式就会变得很麻烦,无法很好地扩展。这就是Mock对象和Moq可以让事情变得简单的地方。在Moq中,三个测试的相同设置如下:
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);
mock.Setup(foo => foo.DoSomething("pong")).Returns(true);
mock.Setup(foo => foo.DoSomething(It.IsAny<string>())).Returns(false);
在我看来,这更简单且更能表达您对
IFoo
依赖项行为的期望。
至于指南,我认为你最好阅读关于模拟的一般性指南,而不是针对Moq的专门指南。Moq只是一个让使用模拟对象更容易的库,而
快速入门是使用Moq的机制的很好的参考。有许多关于模拟的教程和指南,只需搜索一些即可。记住,直到我开始使用它们,才真正“点燃”了我的灵感。找一个
编码卡塔并尝试自己模拟!