【问题标题】:How to write a unit test for the methods that change data?如何为更改数据的方法编写单元测试?
【发布时间】:2013-11-01 17:14:54
【问题描述】:

我有以下方法:

Void UpdateUser(User user){}

我需要检查这个方法是否能正常工作。

我在单元测试中使用了一个单独的数据库来检查这一点。但是很多有经验的人说如果我用这种方法就不是单元测试了;这就是集成测试。

但我不知道如何模拟单元测试。

UpdateUser 方法中编写的代码,将尝试使用实体框架更新数据。

如果我模拟(实际上我也不怎么做),这将如何与实体框架一起工作?

【问题讨论】:

  • 有数百个博客和数千个关于使用实体框架模拟数据库的问题。你尝试了什么,你发现了什么,什么没用?
  • 您试图解决的根本问题是如何实现依赖注入。我建议从那里开始你的研究。 Why does one use dependency injection?
  • 请发布您的自定义数据上下文类 - 包含 DbSet<T> 属性的类,以便我可以发布与您的设置匹配的答案。

标签: c# .net entity-framework unit-testing mocking


【解决方案1】:

模拟意味着您开发软件组件(类)的方式是,任何具有行为的类都作为接口(或抽象类)使用/使用/调用。您对抽象进行编程。运行时您使用某些东西(服务定位器、DI 容器、工厂...)来检索/创建这些实例。

最常见的方式是使用构造注入Here 很好地解释了为什么要使用 DI,以及如何使用的示例。

在您的情况下,使用实体框架的组件(例如您的存储库)必须实现存储库接口,并且使用您的存储库的任何类都应将其用作接口。

这样,您可以在单元测试中模拟存储库。这意味着您创建一个单元测试存储库类(与任何数据库或 EF 无关),并在创建要进行单元测试的类的实例时使用它。

希望这会有所帮助。有很多来源可以找到。就我个人而言,我刚刚阅读了this book,我发现它非常好。 This 是作者的博客。

【讨论】:

    【解决方案2】:

    您可以使用事务和回滚或创建测试用户尝试其更新。断言,然后在 finally 块中删除测试用户。

    您可以使用 moq、rhino 等模拟框架。moq 非常简单,您可以找到许多使用 DI 演示 moq 的示例,例如 unity 框架。

    【讨论】:

    • -1 回滚数据库是测试,但不是单元测试
    • @KeithPayne,请发表您对我所问问题的回答。我必须进行单元测试,而不是集成测试。请发表您的宝贵答案
    • @DavidArno 因为您不能在单元测试的上下文中锁定数据库以防止另一个进程修改底层数据,所以它不能是确定性的。如果同时运行其他类似的依赖于数据库的测试,并且所有这些测试都没有根据所有其他可能测试的知识进行编码以防止意外数据,它将失败。
    【解决方案3】:

    如果你的课是这样的

    public class UserRepository()
    {
        Sqlcontext _context;
        void UpdateUser(User user)
        {
           _context.Users.Add(user);
        }
    }
    

    那么这不是可单元测试的。

    虽然这不是单元测试,但如果你坚持连接数据库并测试它,你可以将你的功能更改为

    User UpdateUser(User user)
    {
        _context.Users.Add(user);
        return user;
    }
    

    并测试是否

    user.Id > 0  
    

    在这里,您基本上只是在测试实体框架。

    【讨论】:

      【解决方案4】:

      “我在单元测试中使用了一个单独的数据库来检查这一点。但是很多 有经验的人说如果我用这个方法就不会单位了 测试;这就是集成测试。”

      那些人是错误的,尽管他们应该有经验。出于某种原因,单元测试都是关于孤立地测试部分代码的错误观念近年来越来越流行。实际上,单元测试就是编写作为一个单元的测试,换句话说,它们是孤立存在的,一个单元测试的结果不会影响另一个测试。

      如果你的UpdateUser方法直接访问EF,那么只要保证每次测试结束时保证数据库回滚到起始状态,那么你就有了单元测试。但是,为每个测试设置数据库并确保它可以可靠地回滚可能需要做很多工作。这就是为什么经常使用模拟。其他答案已经涵盖了 mcoking EF,所以我不再赘述。

      为了大大简化您的测试,您可以在UpdateUser 和 EF 之间设置一个外推层。换句话说,UpdateUser 类提供了一个接口实例,这是它进入 EF 的网关。它不直接与 EF 对话。然后要模拟 EF,您只需提供接口的模拟实现。然后,这将针对 EF 进行测试的需求推向了更基本的层,具有更基本的类似 CRUD 的行为。

      【讨论】:

      • 尽管您对其他人的假设经验更加尊重,但问题不在于隔离,而在于数据库的性质。如果 OP 在每个线程上使用一个单独的数据库实例,那么通过数据库进行测试可以是一个单元测试。只要测试是快速的、自动化的、隔离的和确定性的,即使是从单独的组件反弹的测试也可以是单元测试。确定性是测试数据库时的杀手锏。没有任何方法可以消除来自其他进程/线程的意外数据的可能性。
      • @KeithPayne,该死的,我发现自己不得不同意你的观点。 :) 当考虑并行运行测试时,数据库测试确实不能是单元测试。真正的单元测试必须支持并行运行。
      【解决方案5】:

      在测试时使用内存数据库(上下文)是一个非官方的技巧,不是最佳实践。

      【讨论】:

        【解决方案6】:

        使用事务并在测试结束时回滚您的事务

        【讨论】:

        • 是的,这就是我在做什么?但不管怎样,人们说要嘲讽。我也觉得这是编程的好方法
        猜你喜欢
        • 1970-01-01
        • 2012-03-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-12
        • 2020-06-30
        • 1970-01-01
        • 1970-01-01
        • 2018-07-31
        相关资源
        最近更新 更多