为什么要使用Mocking框架?

5

我们目前使用Autofac作为IoC容器,遵循DI模型。

最近,我们开始研究MOQ和Rhino Mocks等模拟框架。然而,我们似乎无法证明它们的使用比仅为每个接口创建模拟实现类更有价值。

为什么要这样做:

var mock = new Mock<IFoo>();
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);

不要这样写:

class FooMock : IFoo {
  bool DoSomething(string input) {
    return input == "ping";
  }
}
mock = new FooMock();

后者更冗长,但似乎更灵活,适用于复杂的模拟。

这里有一个类似的问题,可能会提供一些见解:https://dev59.com/D3VC5IYBdhLWcg3wbQfA - Jason Down
@Jason Down:我认为这不是一个“类似”的问题,而是同样的问题。我投票关闭重复。 - Don Roby
@Don Roby:是的,我想这基本上就是同一个问题。 - Jason Down
我一直觉得使用依赖注入来解决模拟对象比直接新建一个模拟对象更好,因为模拟对象可能也有依赖关系,如果仅使用模拟,则必须手动设置依赖项,但是可以通过 DI 框架自动连接。 - liang
3个回答

12
在你的示例中展示的更像是一个伪造/存根,而不是真正的模拟,如果你只想要从一个依赖对象中得到预先设定好的行为,那么使用一个伪造可能比使用一个模拟框架更好。Martin Fowler有一篇经典文章讨论了Mocks aren't Stubs这个事实,我摘抄了以下段落:
关键区别在于我们如何验证订单与仓库的交互是否正确。通过状态验证,我们可以断言仓库的状态。而模拟使用行为验证,在这种情况下,我们检查订单是否对仓库进行了正确的调用。
通常情况下,使用模拟来验证您的被测方法对依赖项的影响-您的模拟具有期望值,并在方法运行后验证这些期望值。
当然,你仍然可以编写自己的特定案例模拟,但使用框架将节省大量时间,在测试中提供更可读的代码并减少测试中的错误。尤其是当你的期望变得更加复杂时,想象一下有一个需要测试的方法,它以可变数量的方式调用一个特定的依赖类,并根据输入参数的不同值进行变化-这将很难自己编写一个模拟,但使用一个好的模拟框架却很容易。
为了演示一些代码,想象一下这个PrintOrders方法(请原谅这个愚蠢的例子):
public void PrintForeignOrders(List<Orders> orders)
{
    foreach(var order in orders)
    {
        if (order.IsForeign)
        {
            printer.PrintOrder(order.Number, order.Name);
        }
    }
}

您可能至少想要测试以下内容:

  • 当列表为空时,不打印任何内容
  • 当存在外来订单时,将其打印出来
  • 当存在两个订单时,都会被打印出来(带有正确的编号和名称)

使用良好的模拟框架设置针对注入的打印机对象的这些测试只是几个按键的事情。


9

每当您更改接口时,那么每个手动编写的模拟对象(事实上是一个存根)都必须进行调整,以符合新的接口。

因此,拥有很多手动编码的存根对象将导致每次更改接口时需要维护大量的测试代码。而由模拟框架创建的模拟对象将始终与更新后的接口兼容。


7
然而,我们似乎无法证明它们的使用比仅为每个接口创建Mock实现类更有价值。
这正是mocking框架可以帮助你避免的 - 创建一个存根或模拟实现只是为了测试你的类的行为。
你所做的并不错,如果对于你的项目来说可行,请坚持使用 - 对于许多人来说,这似乎是一项非常平凡的任务,这就是使用mocking框架可以节省大量时间,并避免代码库膨胀只是为了测试和模拟实现中可能出现的额外错误的地方。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接