如何使用最小起订量模拟表中的每一行

本文关键字:一行 何使用 模拟 | 更新日期: 2023-09-27 18:30:37

我是C#和Moq新手。我有一些如下所示的代码,我想使用 Moq 对其进行单元测试。

Data.Foo.FooDataTable tbl = Adapter.GetFooByID(id);
foreach (Data.Foo.FooRow row in tbl)
{
    x = row.bar
    ...
}

如何设置模拟?当前中断的尝试:

var adapter = new Mock<FooTableAdapter>();
var table = new Mock<Foo.FooDataTable>();
var rows = new Mock<DataRowCollection>();
var row = new Mock<Foo.FooRow>();
rows.Setup(x => x.GetEnumerator().Current).Returns(row.Object);
table.Setup(x => x.Rows).Returns(rows.Object);
adapter.Setup(x => x.GetFooByID(1)).Returns(table.Object);
_adapter = adapter.Object;

如果我不尝试添加该行,我会在 foreach 中得到一个 NullReferenceException。如果我尝试添加该行,我会得到一个 System.NotSupportedException:模拟类型必须是接口或抽象或非密封类。

如何使用最小起订量模拟表中的每一行

模拟很棒,但它们确实是最后的测试工具 - 当你有一些无法创建无法避免的对象时,你会达到什么 - 例如HttpContext。

在这种情况下,您可能不想创建 DataTable 的最小起订量模拟 - 您可以使用适当的数据新建一个。你想要模拟的是调用 Adapter.GetFooById() 来吐回数据表的测试双倍。

只有当你

想测试需要所述依赖关系的行为时,模拟才应该用于创建假依赖项,但你不想(或不能)实际创建该依赖项的"真实"实例。任何具有多个模拟的测试方法都朝着错误的方向发展,因为这表明您有太多的依赖项,或者您正在测试太多不相关的东西。

在上面的代码中,没有依赖项,因此 Mocks 并不适合您真正需要的。

真的需要考虑你在这里要测试的到底是什么。为了便于论证,我们假设您显示的代码来自一个方法:

public class MyFooClass
{
   public int DoFooFooData(FooAdapter Foo)
   {
     Data.Foo.FooDataTable tbl = Adapter.GetFooByID(id);
     //just imagining what you might do here.
     int total=0;
     foreach (Data.Foo.FooRow row in tbl)
     {
       x = row.bar
       //just imagining what you might do here.
       total+=x;
    }
    return total;
  }
}
  • 现在,让我们进一步假设您要对此方法进行单元测试。在这种情况下,为了调用该方法,您必须提供一个工作FooAdapter实例,因为该方法依赖于它才能工作

  • 但是现在假设您当前没有拥有FooAdapter,因为它不存在,或者您可能无法提供FooAdapter因为建立了数据库连接,这在单元测试中是禁忌的。

为了测试DoFooFooData,我们需要做的是提供一个假的(Mock)FooAdapter,它只实现GetFooByID方法,以便你的函数执行。

为此,您必须FooAdapter抽象或(我建议)通过接口声明它:

public interface IFooAdapter { Data.Foo.FooDataTable GetByID(int id); }

(稍后你需要更改FooAdapter来实现IFooAdapter如果你想真正地将它与DoFooFooData方法一起使用)

现在更改方法签名:

public void DoFooFooData(IFooAdapter Foo)
    {
       Data.Foo.FooDataTable tbl = Adapter.GetFooByID(id);
         int total=0;
         foreach (Data.Foo.FooRow row in tbl)
         {
           x = row.bar
           //just imagining what you might do here
           total+=x;
        }
        return total;
    }

最后,在您的测试方法中,您可以模拟此依赖项:

 void DoFooFooData_DoesSomeFooAndReturns3()
    {
      var mock = new Mock<IFooAdapter>();
      var table = new Data.Foo.FooDataTable();
      table.Add(new Data.Foo.FowRow{bar=1});
      table.Add(new Data.Foo.FowRow{bar=2});
      mock.Setup(m=>m.GetByID(It.IsAny<int>()).Returns(table);
      var sut = new MyFooClass();
      var expected=3;
      var actual=sut.DoFooFooData(mock.Object);
      Assert.AreEqual(expected,actual);
    }

当然,如果你也需要模拟FooDataTable,你可以遵循与IFooAdapter相同的模式,但你需要在这一点上停下来,问问自己是否不应该创建一个单独的测试,在其中模拟一个IFooDataTable并确保它做它应该做的事情(添加方法或其他什么)等等......当你确定IFooDataTable的行为契约是正常的时,你会把它实现为一个具体的"存根",然后你可以用它来代替IFooAdapter上下文中的任何FooDataTable引用......但是现在你进入了集成测试,这是另一天的故事......