当前位置: 首页 > 知识库问答 >
问题:

人们是如何使用实体框架6进行单元测试的?

岳承悦
2023-03-14

我昨天问了一个包括这个的问题,但它似乎是一个独立的问题。我已经坐下来开始实现一个服务类,我将使用该服务类从控制器中抽象业务逻辑,并使用EF6映射到特定的模型和数据交互。

问题是我自己已经遇到了障碍,因为我不想在存储库中抽象EF(它仍然可以在服务之外用于特定的查询等),并且希望测试我的服务(将使用EF上下文)。

我想问题是这样做有意义吗?如果是这样,那么在IQueryable引起的泄漏抽象和Ladislav Mrnka关于单元测试主题的许多伟大文章中,由于Linq提供程序在处理内存实现时与处理特定数据库时的差异,单元测试不是很直接,人们是如何在野外进行的呢?

我要测试的代码看起来很简单。(这只是尝试和理解我在做什么的虚拟代码,我想使用TDD驱动创建)

上下文

public interface IContext
{
    IDbSet<Product> Products { get; set; }
    IDbSet<Category> Categories { get; set; }
    int SaveChanges();
}

public class DataContext : DbContext, IContext
{
    public IDbSet<Product> Products { get; set; }
    public IDbSet<Category> Categories { get; set; }

    public DataContext(string connectionString)
                : base(connectionString)
    {

    }
}

服务

public class ProductService : IProductService
{
    private IContext _context;

    public ProductService(IContext dbContext)
    {
        _context = dbContext;
    }

    public IEnumerable<Product> GetAll()
    {
        var query = from p in _context.Products
                    select p;

        return query;
    }
}

目前我的心态是做几件事:

  1. 用类似这样的方法来模拟EF上下文--在单元测试时模拟EF或者直接在接口上使用模拟框架(如moq)--承受单元测试可能通过但不一定能端到端工作的痛苦,并用集成测试来备份它们?
  2. 也许用一些类似努力的东西来嘲弄EF--我从来没有用过它,也不知道是否有人在野外使用它?
  3. 不需要测试任何简单地回调EF的东西-所以本质上直接调用EF的服务方法(getAll等)不是单元测试,而是集成测试?

有没有人在没有回购的情况下真的这么做,并且成功了?

共有1个答案

储思聪
2023-03-14

这是一个我很感兴趣的话题。有很多纯粹主义者说,你不应该测试EF和NHibernate这样的技术。他们是正确的,他们已经非常严格的测试,正如前面的回答所说,花大量的时间来测试你不拥有的东西通常是没有意义的。

然而,您确实拥有下面的数据库!在我看来,这就是这种方法的缺点,你不需要测试EF/NH是否正确地完成了他们的工作。您需要测试映射/实现是否与数据库一起工作。在我看来,这是您可以测试的系统中最重要的部分之一。

然而,严格地说,我们正在离开单元测试的领域,进入集成测试,但原则是相同的。

您需要做的第一件事是能够模拟您的DAL,这样您的BLL可以独立于EF和SQL进行测试。这些是您的单元测试。接下来,您需要设计您的集成测试来证明您的DAL。在我看来,这些测试同样重要。

有几件事需要考虑:

  1. 每次测试时,数据库都需要处于已知状态。大多数系统为此使用备份或创建脚本。
  2. 每个测试必须是可重复的
  3. 每个测试必须是原子测试

设置数据库有两种主要方法,第一种是运行UnitTest create DB脚本。这可以确保您的单元测试数据库在每个测试开始时始终处于相同的状态(您可以重置此,也可以在事务中运行每个测试以确保这一点)。

您的另一个选择是我所做的,为每个单独的测试运行特定的设置。我认为这是最好的方法,主要有两个原因:

  • 您的数据库更简单,不需要为每个测试提供完整的模式
  • 每个测试都更安全,如果更改创建脚本中的一个值,则不会使其他几十个测试无效。

不幸的是,你在这方面的妥协是速度。运行所有这些测试,运行所有这些设置/拆分脚本都需要时间。

最后一点,编写如此大量的SQL来测试ORM可能是一项非常艰苦的工作。这是我采取一个非常恶劣的方法的地方(这里的纯粹主义者会不同意我的观点)。我使用我的ORM来创建我的测试!我的系统中的每个DAL测试都没有单独的脚本,我有一个测试设置阶段来创建对象,将它们附加到上下文并保存它们。然后我运行我的测试。

这远远不是理想的解决方案,但在实践中,我发现它更容易管理(尤其是当您有数千个测试时),否则您将创建大量的脚本。实用性胜过纯洁性。

毫无疑问,我会在几年后(几个月/几天)回顾这个答案,并不同意自己的看法,因为我的方法已经改变了--但这是我目前的方法。

下面是我典型的DB集成测试:

[Test]
public void LoadUser()
{
  this.RunTest(session => // the NH/EF session to attach the objects to
  {
    var user = new UserAccount("Mr", "Joe", "Bloggs");
    session.Save(user);
    return user.UserID;
  }, id => // the ID of the entity we need to load
  {
     var user = LoadMyUser(id); // load the entity
     Assert.AreEqual("Mr", user.Title); // test your properties
     Assert.AreEqual("Joe", user.Firstname);
     Assert.AreEqual("Bloggs", user.Lastname);
  }
}

这里要注意的关键是,两个循环的会话是完全独立的。在RunTest的实现中,必须确保上下文被提交和销毁,并且数据只能来自第二部分的数据库。

编辑13/10/2014

我确实说过我可能会在接下来的几个月修改这个模型。虽然我在很大程度上支持我上面所提倡的方法,但我稍微更新了我的测试机制。我现在倾向于在TestSetup和TestTearDown中创建实体。

[SetUp]
public void Setup()
{
  this.SetupTest(session => // the NH/EF session to attach the objects to
  {
    var user = new UserAccount("Mr", "Joe", "Bloggs");
    session.Save(user);
    this.UserID =  user.UserID;
  });
}

[TearDown]
public void TearDown()
{
   this.TearDownDatabase();
}

然后分别测试每个属性

[Test]
public void TestTitle()
{
     var user = LoadMyUser(this.UserID); // load the entity
     Assert.AreEqual("Mr", user.Title);
}

[Test]
public void TestFirstname()
{
     var user = LoadMyUser(this.UserID);
     Assert.AreEqual("Joe", user.Firstname);
}

[Test]
public void TestLastname()
{
     var user = LoadMyUser(this.UserID);
     Assert.AreEqual("Bloggs", user.Lastname);
}

这种做法有几个原因:

  • 没有其他数据库调用(一次设置,一次拆除)
  • 测试的粒度要大得多,每个测试都验证一个属性
  • 设置/拆分逻辑将从测试方法本身中删除

我觉得这使得测试类更简单,测试更粒度化(单个断言是好的)

编辑5/3/2015

对这一方法的另一个修订。虽然类级别的设置对于测试(如加载属性)非常有用,但在需要不同设置的情况下,它们就不那么有用了。在这种情况下,为每种情况设置一个新类是矫枉过正的。

singlesetup中,我们有一个与我在第一次编辑中描述的非常相似的机制。一个例子是

public TestProperties : SingleSetup
{
  public int UserID {get;set;}

  public override DoSetup(ISession session)
  {
    var user = new User("Joe", "Bloggs");
    session.Save(user);
    this.UserID = user.UserID;
  }

  [Test]
  public void TestLastname()
  {
     var user = LoadMyUser(this.UserID); // load the entity
     Assert.AreEqual("Bloggs", user.Lastname);
  }

  [Test]
  public void TestFirstname()
  {
       var user = LoadMyUser(this.UserID);
       Assert.AreEqual("Joe", user.Firstname);
  }
}

但是,确保只加载正确的实体的引用可以使用SetupPerTest方法

public TestProperties : SetupPerTest
{
   [Test]
   public void EnsureCorrectReferenceIsLoaded()
   {
      int friendID = 0;
      this.RunTest(session =>
      {
         var user = CreateUserWithFriend();
         session.Save(user);
         friendID = user.Friends.Single().FriendID;
      } () =>
      {
         var user = GetUser();
         Assert.AreEqual(friendID, user.Friends.Single().FriendID);
      });
   }
   [Test]
   public void EnsureOnlyCorrectFriendsAreLoaded()
   {
      int userID = 0;
      this.RunTest(session =>
      {
         var user = CreateUserWithFriends(2);
         var user2 = CreateUserWithFriends(5);
         session.Save(user);
         session.Save(user2);
         userID = user.UserID;
      } () =>
      {
         var user = GetUser(userID);
         Assert.AreEqual(2, user.Friends.Count());
      });
   }
}

总之,这两种方法的工作取决于您试图测试的内容。

 类似资料:
  • 问题内容: 有人可以帮我这个忙。我正在使用Jersey休息测试框架版本2.21(在Grizzly容器上)编写Rest资源的单元测试。 当我调试测试类时,看到myManager的模拟对象。但是,当调试进入“ MyResouce类”时,myManager对象将变为null并得到NullPointer异常。 尝试过其他人提供的解决方案,但是没有运气。请有人帮我。我将近三天就遇到这个问题。:( 我的资源类

  • 单元测试,对独立的代码功能片段,由编写代码的团队进行测试,也是一种编码,而非与之不同的一些事情。设计代码的一部分就是设计它该如何被测试。你应该写一个测试计划,即使它只是一句话。有时候测试很简单:“这个按钮看起来好吗?”,有时候它很复杂:“这个匹配算法可以精确地返回正确的匹配结果?”。 无论任何可能的时候,使用断言检查以及测试驱动。这不仅能尽早发现 bug,而且在之后也很有用,让你在其他方面担心的谜

  • 问题内容: 我有一个Spring MVC应用程序(Spring Boot 1.2.5版),该应用程序使用JPA与流行的Sql数据库进行交互。 因此,我有几个映射数据库中所有表的实体。显然,这些类仅具有用于实体之间关系的获取器/设置器和注释。 例如: 我的问题是:我应该对这些课程进行单元测试吗?我应该测试什么?怎么样 问题答案: 我建议您测试所有编写的内容(或选择编写)…因此在这种情况下,我看到以下

  • 本文向大家介绍使用Go进行单元测试的实现,包括了使用Go进行单元测试的实现的使用技巧和注意事项,需要的朋友参考一下 简介 日常开发中, 测试是不能缺少的. Go 标准库中有一个叫做 testing 的测试框架, 可以用于单元测试和性能测试. 它是和命令 go test 集成使用的. 测试文件是以后缀 _test.go 命名的, 通常和被测试的文件放在同一个包中. 单元测试 单元测试的格式形如: 在

  • 问题内容: 我选择的数据库是MongoDB。我正在编写一个数据层API,以从客户端应用程序中抽象实现细节- 也就是说,我实质上是在提供一个公共接口(一个充当IDL的对象)。 我正在以TDD方式测试自己的逻辑。在每个单元测试之前,调用一个方法来创建数据库单例,此后,当测试完成时,将调用一个方法来删除数据库。这有助于促进单元测试之间的独立性。 几乎所有单元测试(即 执行上下文查询 )都需要先进行某种插

  • 我想测试以下骆驼路线。我在网上找到的所有例子都有以文件开头的路由,在我的例子中,我有一个Springbean方法,每隔几分钟就会被调用一次,最后消息被转换并移动到jms以及审计目录。 我对这条路线的写测试毫无头绪。目前我在测试用例中所拥有的是