【问题标题】:Unit Testing practice with Linq to SQL使用 Linq to SQL 进行单元测试实践
【发布时间】:2011-01-10 17:52:17
【问题描述】:

我正试图将注意力集中在单元测试上,但我遇到了一个我不确定的行为:

“可以备份库存”

基本上,“Inventory”表被复制到“InventoryHistory”表中,并给出了备份发生时间的时间戳(“HistoryDate”)。

这是备份库存的代码:

        DateTime historyDate = DateTime.Now;
        MyDataContext db = new MyDataContext();

        db.GetTable<InventoryHistory>().InsertAllOnSubmit(
            db.GetTable<Inventory>()
                .Select(i => new InventoryHistory
                {
                    ID = i.ID,
                    ItemName = i.ItemName,
                    /* etc, etc, etc */
                    HistoryDate = historyDate

                })
        );

我的问题是:

  1. 应该/可以将此行为分解为更小的可单元测试部分吗?

  2. 由于我正在针对专用测试数据库进行测试,我是否应该使用模拟工具并遵循任何“存储库”的抽象工厂模式?

【问题讨论】:

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


    【解决方案1】:

    我要问的问题是,这真的是一个单元测试吗?单元测试会考虑模拟Table&lt;TEntity&gt; 实例,因为我们不关心实际数据,而是关心创建项目的机制是否正确。

    在您上面的 sn-p 中,您似乎在对 Linq 方法本身进行单元测试,而不是您自己编写的任何特定代码。

    至于您的最后一个问题,模拟时犯的基本错误之一是模拟时假设要测试什么。通常你会嘲笑你想要测试的类型所消耗的东西。例如:

    public ICalculatorService
    {
      int Add(int a, int b);
    }
    
    [Test]
    public void CannAdd()
    {
      var mock = Mock<ICalculatorService();
      mock.Setup(m => m.Add(It.IsAny<int>(), It.IsAny<int>()))
          .Returns(100);
    
      var service = mock.Object;
      Assert(service.Add(1, 2) == 100); // Incorrect
    }
    

    以上是一个毫无意义的测试,因为我正在测试它是否返回了我告诉它的内容。我不是在这里测试 Moq 框架,我需要测试我的代码,所以我需要测试消费者:

    public class Calculator
    {
      private readonly ICalculatorService _service;
    
      public Calculator(ICalculatorService service)
      {
        _service = service;
      }
    
      public int Add(int a, int b)
      {
        return _service.Add(a, b);
      }
    }
    
    [Test]
    public void CannAdd()
    {
      var mock = Mock<ICalculatorService();
      mock.Setup(m => m.Add(It.IsAny<int>(), It.IsAny<int>()))
          .Returns(100);
    
      var calculator = new Calculator(mock.Object);
      Assert(calculator.Add(1, 2) == 100); // Correct
    }
    

    这更像是它(虽然是一个简单的例子)。我现在正在测试Calculator 消费者本身,而不是消耗品。在您的示例中,即使您正在模拟您的 DataContext 以返回 Table&lt;TEntity&gt; 的虚拟实例,您会得到什么真正的好处?

    实际上,您可能会创建一个存储库,例如IInventoryRepository,并创建该存储库的使用者(可以是域模型、控制器等)。然后通过测试,您将模拟该存储库,并测试您的使用者。

    【讨论】:

    • 谢谢您,这非常有用,尽管我对此还有些不清楚。根据您的回答,我的总结是“可以备份库存”行为的单元可测试性将取决于使用它的上下文?这有点令人沮丧,因为我认为“可以备份库存”是一种非常简单的行为,尽管在单元测试方面,它更加模糊。
    • @Eric - 你的总结是正确的如果你需要模拟你的数据上下文。问题是您的特定代码段非常原子,并且要从单元测试该代码段中获得任何价值,您需要模拟您的数据上下文,以便您的测试证明 InventoryHistory 实例是为存在于数据上下文中的一组模拟 Inventory 项。如果是这种情况,则创建数据上下文的模拟,模拟GetTable&lt;Inventory&gt; 表以返回您的测试数据,并编写测试以证明已创建相关的历史记录项。
    • 但同样,您的左侧测试 Linq-to-Sql 方法InsertOnSubmit 是正确的,这在您的单元测试范围内吗?
    • 对调用存储库的控制器进行单元测试是通过伪造一个接口(如果它使用抽象工厂模式)来完成的,但是您将如何伪造(存根和模拟)一个 EF 数据上下文?
    【解决方案2】:

    对我来说,这看起来像是一个相当原子的操作,没有多少机会将其分解。

    单元测试不会命中数据库——这是集成测试。如果您想要对此进行良好的单元测试,您将测试行为 - 在这种情况下,历史记录应该在它应该的时候被备份。

    【讨论】:

    • Pex 和 Moles 很适合伪造时间等。你怎么能伪造 DataContext?
    【解决方案3】:

    通过全面披露,我才刚刚开始学习 EF 和 LINQ 以及测试它们的最佳方法,因此您可能会获得一些特别有关它们的更有用的信息,所以这些只是一般的测试答案。

    1。 除了:

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    

    被重构为单独的方法以进行单元测试,因为唯一的其他代码是 LINQ 调用,MS 负责测试。

    2。 我认为您不能引入 seam 以将其与抽象存储库工厂模式隔离,因为您正在调用数据上下文。

    我不确定你是否应该fake这个(因为你要测试它将是一个mock正确的 - 你测试的一个假货,你不测试的假是一个存根),但由于它调用一个测试服务器,你可以使它成为一个自动集成测试,因为功能涉及交互与数据存储。

    【讨论】:

    • 单元测试涉及将代码与其交互的其他对象完全隔离。这意味着如果代码失败,您可以确定失败与被测代码有关。为此,您必须伪造这些对象。存储库的职责是调用数据库,它与数据库的交互如此之多,以至于隔离它并没有像测试它是否与数据库正确工作一样给您带来更多价值。
    【解决方案4】:

    起初,您描述的方法看起来很简单,我不确定它是否需要任何单元测试。 但是,如果你想分解它,你可以这样做:

    1. 获取备份清单列表的提取方法

      IQueryable GetInventoryForBackup(此 DataContext 上下文) { 返回上下文.GetTable(); }

    2. Extract 方法将 Inventory 转换为 InventoryHistory

      IEnumerable ToInventoryHistory(这个 IEnumerable 数据,DateTime historyDate) { return data.Select(i => new InventoryHistroy { ID = i.Id .... }

    3. 保存InventoryHistory序列的提取方法

      void SaveHistory(IEnumerable 数据) { dataContext.InsertAllOnSubmit(数据); dataContext.SubmitChanges(); }

    现在你有了看起来不错的方法,你可以很容易地为它编写单元测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-19
      • 1970-01-01
      • 2010-12-15
      • 2019-03-12
      • 1970-01-01
      相关资源
      最近更新 更多