C# 使用Linq到SQL的单元测试实践

C# 使用Linq到SQL的单元测试实践,c#,.net,unit-testing,linq-to-sql,tdd,C#,.net,Unit Testing,Linq To Sql,Tdd,我正试图对单元测试进行思考,我遇到了一个我不确定的行为: ID = i.ID, ItemName = i.ItemName, /* etc, etc, etc */ HistoryDate = historyDate “可以备份库存” ID = i.ID, ItemName = i.ItemName, /* etc, etc, etc */ HistoryDate = historyDate 基本上,“Inventory”表被复制到“InventoryHistory”表中,并给出备份发生的时

我正试图对单元测试进行思考,我遇到了一个我不确定的行为:

ID = i.ID,
ItemName = i.ItemName,
/* etc, etc, etc */
HistoryDate = historyDate
“可以备份库存”

ID = i.ID,
ItemName = i.ItemName,
/* etc, etc, etc */
HistoryDate = historyDate
基本上,“Inventory”表被复制到“InventoryHistory”表中,并给出备份发生的时间戳(“HistoryDate”)

ID = i.ID,
ItemName = i.ItemName,
/* etc, etc, etc */
HistoryDate = 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

                })
        );
ID = i.ID,
ItemName = i.ItemName,
/* etc, etc, etc */
HistoryDate = historyDate
DateTime historyDate=DateTime.Now;
MyDataContext db=新的MyDataContext();
db.GetTable().InsertAllOnSubmit(
db.GetTable()
.选择(i=>new Inventory History
{
ID=i.ID,
ItemName=i.ItemName,
/*等等等等等等*/
HistoryDate=历史日期
})
);
我的问题是:

ID = i.ID,
ItemName = i.ItemName,
/* etc, etc, etc */
HistoryDate = historyDate
  • 这种行为是否应该/可以分解为更小的单元可测试部分

  • ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
  • 既然我是针对一个专用的测试数据库进行测试,那么我应该使用模拟工具并遵循任何“存储库”的抽象工厂模式吗

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

    我想问的问题是,这真的是一个单元测试吗?单元测试会考虑嘲讽<代码>表>代码>实例,因为我们不关心实际数据,而是创建项目的机制是正确的。
    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    在上面的代码片段中,您似乎是在对Linq方法本身进行单元测试,而不是自己编写的任何特定代码

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    至于你的最后一个问题,模仿的一个基本错误是假设模仿时要测试什么。通常情况下,您将模拟要测试的类型所使用的某个对象。例如:

    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
    }
    
    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    公共iCalculator服务
    {
    整数相加(整数a、整数b);
    }
    [测试]
    公共添加()
    {
    var mock=mock m.Add(It.IsAny(),It.IsAny())
    .申报表(100份);
    var service=mock.Object;
    断言(service.Add(1,2)==100);//不正确
    }
    
    上面的测试毫无意义,因为我正在测试它是否返回了我告诉它的内容。我不是在测试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
    }
    
    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    公共类计算器
    {
    专用只读ICalculatorService_服务;
    公共计算器(iCalculator服务)
    {
    _服务=服务;
    }
    公共整数相加(整数a、整数b)
    {
    返回服务。添加(a,b);
    }
    }
    [测试]
    公共添加()
    {
    var mock=mock m.Add(It.IsAny(),It.IsAny())
    .申报表(100份);
    var计算器=新计算器(mock.Object);
    Assert(calculator.Add(1,2)==100);//正确
    }
    
    这更像是一个简单的例子。我现在正在测试
    计算器
    消费者本身,而不是消费品。在您的示例中,即使您模拟DataContext以返回
    表的虚拟实例
    ,您能得到什么真正的好处

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

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

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

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

    单元测试不会影响数据库——这是集成测试。如果您想对此进行一个良好的单元测试,您应该测试行为——在本例中,历史记录在应该备份的时候进行备份

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

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    一,。 我看不出有什么方法可以进一步细分,以隔离单元测试,除了:

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    被重构成一个单独的方法进行单元测试,因为唯一的其他代码是LINQ调用,由MS负责测试

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    二,。 我认为您无法引入seam来将其与抽象存储库工厂模式隔离,因为您正在调用datacontext

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

    我不确定你是否应该伪造这个(因为你要测试它将是一个模拟的正确的-一个你测试的伪造,一个你不测试的伪造是一个存根),但是因为它正在调用一个测试服务器,您可以将其设置为自动化集成测试,因为该功能涉及到与数据存储的交互。

    首先,您描述的方法看起来很简单,我不确定它是否需要任何单元测试。
    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    但是,如果你想分解它,你可以这样做:

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
  • 获取要备份的库存列表的提取方法

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    IQueryable GetInventoryForBackup(此DataContext上下文) { 返回context.GetTable(); }

  • ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
  • 将库存转换为库存历史记录的提取方法

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    IEnumerable到InventoryHistory(此IEnumerable数据,日期时间历史日期) { 返回数据。选择(i=>newinventoryHistory{ID=i.ID…)。。。。 }

  • ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
  • 保存InventoryHistory序列的提取方法

    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate
    
    void SaveHistory(IEnumerable数据) { dataContext.InsertAllOnSubmit(数据); dataContext.SubmitChanges(); }

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

    现在,您已经有了一些合适的方法,您可以轻松地编写单元测试。

    谢谢,这是非常有用的,尽管我对此还不太清楚。根据您的回答,我的总结是“can备份库存”的单元可测试性行为将取决于消费的环境?这只是有点令人沮丧,因为我看到了“可以备份库存”作为一种非常简单的行为,尽管就单元测试而言,它更加模糊。@Eric-如果您需要模拟数据上下文,那么您的总结是正确的。问题是您的特定代码段实际上是非常原子化的,为了从单元测试中获得任何价值,您需要模拟数据c
    ID = i.ID,
    ItemName = i.ItemName,
    /* etc, etc, etc */
    HistoryDate = historyDate