【问题标题】:What is the benefits of mocking the dependencies in unit testing?在单元测试中模拟依赖有什么好处?
【发布时间】:2014-04-01 10:54:49
【问题描述】:

我正在为我的控制器和服务层(C#、MVC)进行单元测试。我正在使用 Moq dll 在单元测试中模拟真实/依赖对象。

但我对模拟依赖项或真实对象有点困惑。让我们以下面的单元测试方法为例:-

[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<CustomerDto> 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();
}

在上面的单元测试方法中,我模拟了 GetCustomers 方法并返回一个客户列表。这已经定义了。如下所示:

List<Customer> _customerList = new List<Customer>
{
    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"},
};

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

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

但在这里我不明白它(在单元测试之上)总是可以正常工作。意味着我们只是在测试(在断言中)我们通过了或者我们正在返回(在模拟对象中)。而且我们知道真实/实际对象将始终返回我们传递的列表或对象。

那么这里做单元测试或者模拟是什么意思呢?

【问题讨论】:

    标签: c# unit-testing mocking


    【解决方案1】:

    嘲笑的真正目的是实现真正的隔离。

    假设你有一个CustomerService 类,它依赖于CustomerRepository。您编写了一些单元测试,涵盖了CustomerService 提供的功能。他们都通过了。

    一个月后,做了一些更改,突然您的 CustomerServices 单元测试开始失败 - 您需要找出问题所在。

    所以你假设:

    因为测试CustomerServices 的单元测试失败了,问题一定出在那个类!!

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

    现在想象一个巨大的依赖链:A 依赖于BB 依赖于C,...Y 依赖于Z。如果在Z 中引入了错误,所有您的单元测试都将失败。

    这就是为什么您需要将被测类与其依赖项(可能是域对象、数据库连接、文件资源等)隔离开来。你想测试一个单元

    【讨论】:

      【解决方案2】:

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

      但是,假设您的逻辑基于挂钟时间做了一些事情,比如每小时安排一些进程。在这种情况下,模拟时间源可以让您实际对此类逻辑进行单元测试,这样您的测试就不必运行数小时,等待时间过去。

      【讨论】:

        【解决方案3】:

        除了已经说过的:

        我们可以有没有依赖关系的类。我们唯一拥有的就是没有模拟和存根的单元测试。

        当我们有依赖时,它们有几种:

        • 我们的课程主要以“即发即弃”的方式使用的服务,即不影响消费代码控制流的服务。

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

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

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

        • 影响系统状态或依赖于现实世界的服务 现象或你无法控制的事情。 '@500 - Internal Server Error' 是时间服务的一个很好的例子。

        通过模拟,您可以让时间以所需的速度(和方向)流动。
        另一个例子是使用 DB。在进行单元测试时,通常希望不要更改功能测试不正确的数据库状态。对于此类服务,“隔离”是模拟的主要(但不是唯一)动机。

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

        考虑实体框架:
        SaveChanges() 被调用时,很多事情都会在幕后发生。 EF 检测更改和修复导航属性。此外,EF 不允许您使用相同的键添加多个实体。
        显然,模拟这种依赖关系的行为和复杂性可能非常困难……但如果它们设计得好,通常你就没有。如果你严重依赖某些组件提供的功能,你几乎无法替代这种依赖。可能需要的是再次隔离。您不想在测试时留下痕迹,因此黄油的方法是告诉 EF 不要使用真正的 DB。是的,依赖不仅仅意味着一个接口。更常见的不是方法签名,而是预期行为的契约。例如IDbConnectionOpen()Close() 方法,这意味着一定的调用顺序。

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

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

        假设,Z 是最终依赖项。我们为它创建没有模拟的单元测试。所有边界条件都是已知的。 100% 的覆盖率在这里是必须的。之后我们说 Z 工作正常。如果Z 失败,我们的单元测试必须指出它。
        模拟来自工程。没有人在制造飞机时测试每个螺钉和螺栓。
        使用统计方法可以确定地证明生产细节的工厂工作正常。

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

        我强烈推荐你观看 Roy 的视频:http://youtube.com/watch?v=fAb_OnooCsQ

        【讨论】:

          【解决方案4】:

          在这种情况下,模拟允许您伪造数据库连接,这样您就可以在原地和内存中运行测试,而无需依赖任何额外的资源,即数据库。该测试断言,当调用服务时,会调用 DAL 的对应方法。

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

          在一般情况下,模拟允许:

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

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

          【讨论】:

            猜你喜欢
            • 2016-02-19
            • 2017-09-25
            • 2020-01-05
            • 1970-01-01
            • 2023-03-13
            • 2013-07-07
            • 2016-02-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多