前面介绍了单元测试的框架 NUnit, 它可以很好的帮助我们建立测试, 检验我们的代码是否正确. 但这还不够, 有时候我们的业务比较重, 会依赖其它的类. 基于隔离测试的原则, 我们不希望依赖的其它类影响到我们的测试目标. 这时候 Mock 就显得十分重要了. 当然还有其它因素使得我们必须 Mock 对象, 比如配置文件, DB 等.
提供 Mock 技术的工具很多: Moq,NSubstitute,RhinoMocks,TypeMock,JustMock 等. 开源免费的工具功能局限, 像 Moq, 草根专栏 的博客写得很好. 这里我选择 JustMock, 付费版本可以使用高级功能.
JustMock 开始
安装 JustMock , 从官网下载, 默认安装.
添加 Telerik.JustMock.dll 引用, 在安装目录下, 默认为: C:\Program Files (x86)\Progress\Telerik JustMock\Libraries .
开启使用高级功能
为什么需要 Mock
先看我们需要测试的一个方法:
- /// <summary>
- /// 转账
- /// </summary>
- /// <param name="accountA"></param>
- /// <param name="accountB"></param>
- /// <param name="money"></param>
- /// <returns></returns>
- public double TransferAccounts(BankAccount accountA, BankAccount accountB, double money)
- {
- double transferLimit = 50000.0;// 转账最高限制
- try
- {
- var balanceA = accountA.DrawMoney(money);
- accountB.SaveMoney(money);
- return balanceA;
- }
- catch (Exception ex)
- {
- throw new Exception($"转账失败,{ex.Message}");
- }
- }
测试这个方法的逻辑, 只需要下面这段代码就可以了:
- private BankAccount bankAccountA;
- private BankAccount bankAccountB;
- [SetUp]
- public void Setup()
- {
- bankAccountA = new BankAccount(1000);
- bankAccountB = new BankAccount(1000);
- }
- [Test]
- public void Transfer_Test()
- {
- IBankService bankService = new BankService();
- bankService.TransferAccounts(bankAccountA, bankAccountB, 500);
- Assert.AreEqual(500, bankAccountA.GetBalance());
- Assert.AreEqual(1500, bankAccountB.GetBalance());
- }
但, 如果转账的逻辑变了, 需要判断是否超过当日限制, 那么用户的转账总额就得从数据库或者其它途径获得了, 那么可能代码变成这样子:
- private readonly IBankLimitDao _bankLimitDao;// 获取限制条件的类
- public BankService(IBankLimitDao bankLimitDao)
- {
- _bankLimitDao = bankLimitDao;
- }
- /// <summary>
- /// 转账
- /// </summary>
- /// <param name="accountA"></param>
- /// <param name="accountB"></param>
- /// <param name="money"></param>
- /// <returns></returns>
- public double TransferAccounts(BankAccount accountA, BankAccount accountB, double money)
- {
- double transferLimit = 50000.0;// 转账最高限制
- try
- {
- // 判断 A 是否能转账
- var total = _bankLimitDao.TotalTransferTotal(accountA.AccountId);// 获得限制金额
- if (total>= transferLimit)
- {
- throw new Exception($"超过当日转账限额 {transferLimit}");
- }
- var balanceA = accountA.DrawMoney(money);
- accountB.SaveMoney(money);
- return balanceA;
- }
- catch (Exception ex)
- {
- throw new Exception($"转账失败,{ex.Message}");
- }
- }
这个时候再用真实对象来测试就有点麻烦了. 根据隔离原则, 我们不希望测试 TotalTransferTotal 方法里的逻辑和它的正确性, 它应该在其它地方测试. 这时候 Mock 就显得重要了, 我们可以模拟这个对象, 并且给它一个恰当的值, 让它 "正确" 执行.
所以, 测试代码变成这样子:
- [Test]
- public void Transfer_Test()
- {
- var bankLimit = Mock.Create<IBankLimitDao>();// 模拟对象
- Mock.Arrange(() => bankLimit.TodalDrawTotal(Arg.IsAny<string>())).Returns(500);// 设定一个返回值
- IBankService bankService = new BankService(bankLimit);
- bankService.TransferAccounts(bankAccountA, bankAccountB, 500);
- Mock.Assert(bankLimit);
- Assert.AreEqual(500, bankAccountA.GetBalance());
- Assert.AreEqual(1500, bankAccountB.GetBalance());
- }
- AAA
什么是 AAA?Arrange,Act 和 Assert.AAA 是单元测试中编写代码的模式.
Arrange: 准备, 设置需要测试的对象.
Act: 执行测试的实际代码.
Assert: 验证结果.
一个简单的例子:
这个例子包括创建模拟对象, 标记为 InOrder(), 意为必须调用, 执行方法, 最后用 Mock.Assert 验证.
- public interface IFoo
- {
- void Submit();
- void Echo();
- }
- [Test]
- public void ShouldVerifyCallsOrder()
- {
- // Arrange 模拟对象, 并且设置条件
- var foo = Mock.Create<IFoo>();
- Mock.Arrange(() => foo.Submit()).InOrder();
- Mock.Arrange(() => foo.Echo()).InOrder();
- // Act 执行代码
- foo.Submit();
- foo.Echo();
- // Assert 验证结果
- Mock.Assert(foo);
- }
编写测试方法的时候尽量遵循 AAA 的模式编写, 可以让测试代码更清晰可读.
Mock Behaviors
JustMock 在 Mock 对象的时候有四种不同的行为可以选择.
RecursiveLoose Behavior
默认的选项. 模拟的对象不会出现 null 对象, 递归调用也将创建一个默认的对象, 默认值或者空值.
Loose Behavior
除了设置值, 否则 Loose 创建的对象将是默认值.
CallOriginal Behavior
将会采用最初的模拟对象.
Strict Behavior
采用此行为, 模拟对象必须设置值, 否则会出现 MockException 异常.
下面代码展示不同类型的结果:
- [Test]
- public void Test()
- {
- // Arrange
- var rlFoo = Mock.Create<FooBase>(Behavior.RecursiveLoose);
- var lFoo = Mock.Create<FooBase>(Behavior.Loose);
- var coFoo = Mock.Create<FooBase>(Behavior.CallOriginal);
- var sFoo = Mock.Create<FooBase>(Behavior.Strict);
- Mock.Arrange(() => rlFoo.GetString("y")).Returns("z");
- Mock.Arrange(() => lFoo.GetString("y")).Returns("z");
- Mock.Arrange(() => coFoo.GetString("y")).Returns("z");
- Mock.Arrange(() => sFoo.GetString("y")).Returns("z");
- // Act
- var rlactualX = rlFoo.GetString("x"); // 结果:"" var rlactualY = rlFoo.GetString("y"); // 结果:"z" var lactualX = lFoo.GetString("x"); // 结果: null
- var lactualY = lFoo.GetString("y"); // 结果:"z"
- var coactualX = coFoo.GetString("x"); // 结果:"x"
- var coactualY = coFoo.GetString("y"); // 结果:"z"
- var coactualA = coFoo.GetString("a"); // 结果:"a"
- //var sactualX = sFoo.GetString("x"); // 结果: 出现异常
- var sactualY = sFoo.GetString("y"); // 结果:"z"
- var expectedX = "x";
- var expectedY = "z";
- // Assert
- Assert.AreEqual(expectedX, rlactualX);
- Assert.AreEqual(expectedY, rlactualY);
- }
本篇到这, 下篇再记录一些其它用法.
来源: https://www.cnblogs.com/jimizhou/p/11419364.html