在unit testing中模拟依赖项有什么好处?

我正在为我的控制器和服务层(C#,MVC)进行unit testing。 我正在使用Moq DLL来模拟unit testing中的真实/依赖对象。

但是我对于模拟依赖项或真实对象有点困惑。 让我们举一个下面unit testing方法的例子: –

[TestMethod] public void ShouldReturnDtosWhenCustomersFound_GetCustomers () { // Arrrange var name = "ricky"; var description = "this is the test"; // setup mocked dal to return list of customers // when name and description passed to GetCustomers method _customerDalMock.Setup(d => d.GetCustomers(name, description)).Returns(_customerList); // Act List actual = _CustomerService.GetCustomers(name, description); // Assert Assert.IsNotNull(actual); Assert.IsTrue(actual.Any()); // verify all setups of mocked dal were called by service _customerDalMock.VerifyAll(); } 

在上面的unit testing方法中,我正在模拟GetCustomers方法并返回一个客户列表。 哪个已定义。 如下所示:

 List _customerList = new List { new Customer { CustomerID = 1, Name="Mariya",Description="description"}, new Customer { CustomerID = 2, Name="Soniya",Description="des"}, new Customer { CustomerID = 3, Name="Bill",Description="my desc"}, new Customer { CustomerID = 4, Name="jay",Description="test"}, }; 

让我们来看看客户的Assertion模拟对象和实际对象断言: –

 Assert.AreEqual(_customer.CustomerID, actual.CustomerID); Assert.AreEqual(_customer.Name, actual.Name); Assert.AreEqual(_customer.Description, actual.Description); 

但在这里,我不理解它(在unit testing之上)总是正常工作。 意味着我们只是测试(在Assertion中)我们传递的或我们返回的(在模拟对象中)。 我们知道真实/实际对象将始终返回我们传递的列表或对象。

那么在这里进行unit testing或嘲笑是什么意思呢?

嘲笑的真正目的是实现真正的孤立。

假设您有一个CustomerService类,它取决于CustomerRepository 。 您编写了一些unit testing,涵盖了CustomerService提供的function。 他们都过去了。

一个月后,进行了一些更改,突然您的CustomerServicesunit testing开始失败 – 您需要找到问题所在。

所以你假设:

因为测试CustomerServices的unit testing失败了,所以问题必须在那个类中!

对? 错误! 问题可能出在CustomerServices或其任何依赖项中,即CustomerRepository 。 如果它的任何依赖关系失败,那么被测试的类也可能会失败。

现在描绘了一个巨大的依赖链: A取决于BB取决于C ,… Y取决于Z 如果在Z引入了故障,则所有unit testing都将失败。

这就是为什么你需要将测试中的类与其依赖关系隔离开来(可能是域对象,数据库连接,文件资源等)。 你想测试一个单位

你的例子过于简单化,无法展示嘲弄的真正好处。 那是因为你测试的逻辑除了返回一些数据之外并没有做太多的事情。

但想象一下,你的逻辑基于挂钟时间做了一些事情,比如每小时安排一些过程。 在这样的情况下,模拟时间源可以让你实际unit testing这样的逻辑,这样你的测试就不必运行几个小时,等待时间过去。

除了已经说过的话:

我们可以拥有没有依赖关系的类。 我们唯一拥有的是没有模拟和存根的unit testing。

当我们有依赖关系时,有几种:

  • 我们class级主要以“一劳永逸”的方式使用的服务,即不影响消费代码控制流的服务。

我们可以模拟这些(以及所有其他类型)服务来测试它们被正确调用(集成测试)或仅仅用于注入,因为我们的代码可能需要它们。

  • 提供结果但没有内部状态且不影响系统状态的双向服务。 它们可以被称为复杂的数据转换。

通过模拟这些服务,您可以测试您对不同服务实现变体的代码行为的期望,而无需将所有服务实现。

  • 影响系统状态或依赖于现实世界现象或不受控制的事物的服务。 ‘@ 500 – 内部服务器错误’给出了时间服务的一个很好的例子。

通过模拟,您可以让时间以所需的速度(和方向)流动。
另一个例子是使用DB。 在进行unit testing时,通常不希望改变DB状态,而function测试则不然。 对于这种类型的服务,“隔离”是嘲弄的主要(但不是唯一)动机。

  • 具有内部状态的服务,您的代码依赖于。

考虑entity framework:
调用SaveChanges() ,场景后面会发生很多事情。 EF检测更改和修正导航属性。 此外,EF不允许您使用相同的密钥添加多个实体。
显然,模拟这种依赖的行为和复杂性是非常困难的……但通常你没有设计得好。 如果您严重依赖某些组件提供的function,则几乎无法替代此依赖项。 可能需要的是再次隔离。 您不希望在测试时留下痕迹,因此黄油方法是告诉EF不要使用真正的DB。 是的,依赖意味着不仅仅是一个界面。 更常见的不是方法签名,而是预期行为的合同。 例如, IDbConnection具有Open()Close()方法,这意味着某些调用序列。

当然,这不是严格的分类。 最好将其视为极端。

@dcastro写道: You want to test a unit. 然而,该声明并没有回答你是否应该这样做的问题。
让我们不要打折整合测试。 有时知道系统的某些复合部分出现故障是可以的。
至于@dcastro给出的依赖链,我们可以尝试找到包可能的位置:

假设, Z是最终的依赖。 我们创建没有模拟的unit testing。 所有边界条件都是已知的。 这里必须100%覆盖。 之后我们说Z正常工作。 如果Z失败,我们的unit testing必须指出它。
模拟来自工程。 建造飞机时,没有人测试每个螺钉和螺栓。
统计方法用于certificate工厂生产细节的工作正常。

另一方面,对于系统中非常关键的部分,花时间和模拟依赖的复杂行为是合理的。 是的,可维护性较差的测试越复杂。 在这里,我宁愿将它们称为规范检查。
是的,您的api和测试都可能是错误的,但代码审查和其他forms的测试可以在一定程度上确保代码的正确性。 一旦这些测试在进行一些更改后失败,您需要更改规范和相应的测试或找到错误并用测试覆盖案例。

我强烈建议您观看Roy的video:http: //youtube.com/watch?v = fAb_OnooCsQ

在这种情况下,模拟允许您伪造数据库连接,以便您可以在适当的位置和内存中运行测试,而无需依赖任何其他资源,即数据库。 此测试断言,当调用服务时,会调用相应的DAL方法。

但是,列表的后续断言和列表中的值不是必需的。 正如你正确地注意到你只是断言你“嘲笑”的值被返回。 这在模拟框架本身中很有用,可以断言模拟方法的行为符合预期。 但在你的代码中只是过剩。

一般情况下,模拟允许一个人:

  • 测试行为(当事情发生时,然后执行特定方法)
  • 虚假资源(例如,电子邮件服务器,Web服务器,HTTP API请求/响应,数据库)

相比之下,没有模拟的unit testing通常允许您测试状态。 也就是说,您可以在调用特定方法时检测对象状态的更改。