如何使用最小起订量模拟表中的每一行
本文关键字:一行 何使用 模拟 | 更新日期: 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
引用......但是现在你进入了集成测试,这是另一天的故事......