【问题标题】:Mocking a LinqToSql repository in-memory for use in unit tests在内存中模拟一个 LinqToSql 存储库以用于单元测试
【发布时间】:2012-04-30 00:07:55
【问题描述】:

我正在考虑制作一个可用于单元测试的可模拟 DataContexthere 描述了一种方法。但是,这种方法的问题在于,对存储库的更改会立即生效——在调用 Commit(或任何其他等效于 SubmitChanges)之前。

另一方面,包含正确的 SubmitChanges 行为将涉及从 DataContext 复制大量复杂代码,并可能导致更多错误。

模拟内存存储库(不等待SubmitChanges)的幼稚实现对于使用单元测试是否可行?这通常是如何完成的?

【问题讨论】:

  • 我认为你是在正确的轨道上。如果您想对您对存储库(而不是存储库本身)所做的事情进行单元测试,那么模拟内存存储库是一种很好的方法。您还可以使用模拟框架,例如Moq

标签: c# .net unit-testing linq-to-sql mocking


【解决方案1】:

如果我的理解是正确的,那么您还没有完全确定您的 SUT(被测对象)。

  • 如果您想测试与数据库的连接性,那么我们说的是集成测试,您不应该模拟您的DataContext

  • 如果您想测试调用DataContext逻辑,那么这是一个单元测试

无论如何,我建议您将对DataContext 的调用包装在存储库中,存储库将负责 数据库进行对话,遵循这种方法将使事情变得更容易给你

我会用一个例子来说明它,最后我会推荐几个链接来帮助你编写可测试的代码。 (我总是这么说,我会再说一遍,写测试很容易,真正的努力必须放在写干净的可测试代码上)

public interface IMyRepository
{
   void ChangeEmail(int employeeId, string newEmail);
}
public class MyRepository : IMyRepository
{
   private MyDataContext context;
   public MyRepository(MyDataContext context)
   {
      this.context = context;
   }
   public void ChangeEmail(int employeeId, string newEmail)
   {
      //save your email using your context
   }
}

现在在您的消费者代码中,您将注入您的存储库:

public class MyCommand
{
   public MyCommand(IMyRepository myRepository)
   ...
   public void ChangeEmail(int employeeId, string newEmail)
   {
      //adding condition just to clarify how to test
      if(this.AllowChangeEmail(employeeId))
      {
         this.myRepository.ChangeEmail(employeeId, newEmail);
      }
      else
      {
         throw new DomainException("this should not happen");
      }
   }
   ...
}

我们已经解耦了DataContext 的使用,从您的域代码的角度来看,DataContext 不存在,该域知道的唯一代码是IMyRepository,因为它是一个接口,您可以更改提供商无需重构您的域代码即可轻松更改应用程序的行为

如果你注意到我还没有谈到测试,为什么?因为就像我说的,首先要做的是编写干净的可测试代码(不要误解,应该遵循 TDD,这意味着应该先编写测试,我遵循这种方法只是为了演示)现在我们有这段代码让我们看看测试我们应用程序的逻辑是多么容易 我将手动为IMyRepository 编写一个存根以澄清但在实际代码中应该使用自动模拟对象,使用工具比如AutoFixtureFluentAssertionsMoq

public class MyFakeProvider : IMyRepository
{
   public void ChangeEmail(int employeeId, string newEmail)
   {
      //write some assert here indicating the method was called
   }
}

[Test]
public void MyTest()
{
   var myMock = new MyFakeProvider();
   var sut = new MyCommand(myMock);

   sut.Invoking(x => x.ChangeEmail(3, "my@email.com")).ShouldNotThrow();
}

这些链接只关注一件事:编写可测试的代码:

http://misko.hevery.com/code-reviewers-guide/

http://misko.hevery.com/attachments/Guide-Writing%20Testable%20Code.pdf

http://misko.hevery.com/presentations/

http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded

http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded

http://www.youtube.com/watch?v=-FRm3VPhseI&feature=player_embedded

http://www.youtube.com/watch?v=4F72VULWFvc&feature=player_embedded

【讨论】:

    【解决方案2】:

    什么是被测试的类/组件?如果您不直接测试存储库,而只测试正在使用存储库的东西,那么模拟可以根据您的需要进行。

    但是,如果您想在组件和存储库之间进行端到端的集成测试,则需要另一种方法,例如。针对测试数据库运行。

    【讨论】:

    • 所以你的建议是:使用朴素的模拟DataContext 进行单元测试,使用真正的DataContext 和测试数据库进行集成测试。听起来很合理。我会等待我的反馈,看看在我编写单元测试时,天真的嘲笑 DataContext 是否会导致任何问题。
    【解决方案3】:

    我创建了一个接口来表示存储库,其中有 SubmitChanges、InsertOnSubmit 等 DataContext 实现的东西。此外,您的表的 IQueryable 属性。然后,您的 DataContext 子类可以实现此接口,您无需执行任何操作。对于您的单元测试,您可以只创建一个模拟,并且为了模仿 IQueryables,您可以创建列表并使用 AsQueryable 方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-25
      • 1970-01-01
      • 2021-09-01
      • 2018-12-05
      相关资源
      最近更新 更多