65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MOQ 模拟功能

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2016年3月14日

CPOL

4分钟阅读

viewsIcon

9643

使用 MOQ 模拟功能

这段代码有什么问题?

class Program
{
static void Main(string[] args)
{
DateTime dob = new DateTime(1967, 7, 9);
int days = CalcDays(dob);
Console.WriteLine($”Days: {days}”);
}

    private static int CalcDays(DateTime dob)
{
return (DateTime.Today – dob).Days;
}
}

好吧,这段代码可以运行。但是测试会成为一个问题,因为 DateTime.Today 是一个 非确定性函数。这意味着该函数在不同时间调用时可能会返回不同的值。在这种情况下,很明显,明天该函数将返回与今天或昨天不同的值。因此,如果你想为此函数编写测试,则需要每天更改它。实际上,更改测试以使其通过并不是单元测试的真正意义……

我们该如何解决这个问题?

这里需要做一些工作。让我画出我们将要实现的内容

image

我已经在之前的文章中展示过这种模式。我们将 IClockService 注入到 Date 类中。在实际实现中,我们将 CurrentDate 属性实现为 DateTime.Today

现在,我们可以创建我们的测试项目并编写一些测试。在测试中,我们将模拟 IClockService,以便 CurrentDate 变为确定性的,我们不必每天修改我们的测试。

我们的测试可能如下所示

class FakeClockService : IClockService
{
public DateTime CurrentDate
{
get
{
return new DateTime(1980, 1, 1);
}
}
}

[TestClass()]
public class DateTests
{
[TestMethod()]
public void CalcDaysTest()
{
// arrange
Date dt = new Date(new FakeClockService());
DateTime dob = new DateTime(1967, 7, 9);

        // act
int days = dt.CalcDays(dob);

        // assert
Assert.AreEqual(4559, days);
}
}

正如你所看到的,我实现了一个 FakeClockService,它总是返回一个固定的日期。因此,测试我的代码变得容易。

但是,如果我想用不同的 CurrentDate 伪造值来测试我的代码,我将需要为每个不同的值实现接口。在这种简单的情况下,接口包含 1 个简单的方法,所以除了弄乱我的代码之外,这不是一个大问题。但是当你的接口变得更大时,这会花费大量精力。

进入 MOQ

根据 他们的网站,MOQ 是“最流行和友好的 .NET 模拟框架”。我倾向于同意这一点。

设置 MOQ

开始使用 MOQ 很简单:在测试项目中,安装 MOQ Nuget 包。这将设置你的项目以使用 MOQ。有两种可能的方法来做到这一点。

使用 GUI

在 Visual Studio 中,打开 Nuget 包管理器(工具 > Nuget 包管理器 > 管理解决方案的 Nuget 包…)并找到 MOQ,然后安装最新的稳定版本。

使用 NugetPackage Manager 控制台

如果你喜欢打字,请调出 Nuget Package Manager 控制台(工具 > Nuget 包管理器 > 包管理器控制台)。确保在默认项目中,选择了你的测试项目,然后键入

install-package MOQ

image

现在你将在你的项目引用中找到 2 个额外的引用:MockingMoq

在你的测试中使用 MOQ

[TestMethod()]
public void CalcDaysTestNegative()
{
// arrange
var mock = new Mock<IClockService>();
mock.Setup(d => d.CurrentDate).Returns(new DateTime(1960, 1, 1));

    Date dt = new Date(mock.Object);
DateTime dob = new DateTime(1967, 7, 9);

    // act
int days = dt.CalcDays(dob);

    // assert
Assert.AreEqual(-2746, days);
}

Mock 类位于 Moq 命名空间中,所以不要忘记添加...

using Moq;

...在你的 usings 列表中。

我通过调用 Setup 方法来设置模拟。在这个方法中,我实际上是说:“当有人要求你返回 CurrentDate 时,总是给他们 1960 年 1 月 1 日。”

变量 mock 现在包含 IClockService 的实现。我定义了 CurrentDateproperty 以在 2 行代码中返回 new DateTime(1960, 1, 1)。这保持了我的代码的整洁,因为我不需要为此情况实现另一个伪造类。所以我的代码保持整洁,我可以轻松地测试我所有的边界情况(例如,当 2 个日期相等时)。

为了完整起见:mock 不是 IClockService,但这将是 mock.Object。所以我将 mock.Object 传递给 new Date( )。现在我有一个用于我的测试的确定性函数!

当你的接口变得更大时,你将会更加欣赏 MOQ。当你的接口有 20 个方法,而你只需要 3 个方法时,你只需要模拟这 3 个方法,而不是全部 20 个。你也可以用同样的方式模拟具有抽象或虚拟函数的类。

结论

这绝不是关于如何使用 MOQ 的完整教程。你可以在 https://github.com/Moq/moq4/wiki/Quickstart 找到由 MOQ 的人自己编写的教程。在那里你可以找到 MOQ 的更多功能。

我只是想表明,使用像 MOQ 这样的模拟框架可以让你编写更简单的测试,这样你就少了一个不编写测试的借口!

还有现在你知道了我的出生日期,所以别忘了寄给我一张卡片!

再一点

我想实现一个简单的 GetAge(…) 函数,这应该很容易。但是当我阅读了 StackOverflow 上的这个线程 后,我决定保持简单,只使用 CalcDays(…)。叫我懦夫吧。 Knipogende emoticon

测试愉快!!

使用 MOQ 模拟功能 - CodeProject - 代码之家
© . All rights reserved.