前面介绍了单元测试的框架NUnit,它能够很好的帮助咱们创建测试,检验咱们的代码是否正确。但这还不够,有时候咱们的业务比较重,会依赖其它的类。基于隔离测试的原则,咱们不但愿依赖的其它类影响到咱们的测试目标。这时候Mock就显得十分重要了。固然还有其它因素使得咱们必须Mock对象,好比配置文件,DB等。html
提供Mock技术的工具不少:Moq,NSubstitute,RhinoMocks,TypeMock,JustMock等。开源免费的工具功能局限,像Moq,草根专栏 的博客写得很好。这里我选择JustMock,付费版本可使用高级功能。数据库
安装 JustMock ,从官网下载,默认安装。框架
添加 Telerik.JustMock.dll 引用,在安装目录下,默认为:C:\Program Files (x86)\Progress\Telerik JustMock\Libraries 。工具
开启使用高级功能
单元测试
先看咱们须要测试的一个方法:测试
/// <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}"); } }
测试这个方法的逻辑,只须要下面这段代码就能够了:code
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()); }
但,若是转帐的逻辑变了,须要判断是否超过当日限制,那么用户的转帐总额就得从数据库或者其它途径得到了,那么可能代码变成这样子:htm
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就显得重要了,咱们能够模拟这个对象,而且给它一个恰当的值,让它“正确”执行。对象
因此,测试代码变成这样子:blog
[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?Arrange、Act和Assert。AAA是单元测试中编写代码的模式。
一个简单的例子:
这个例子包括建立模拟对象,标记为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的模式编写,可让测试代码更清晰可读。
JustMock 在Mock对象的时候有四种不一样的行为能够选择。
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); }
本篇到这,下篇再记录一些其它用法。