.net core 单元测试之 JustMock第一篇
前面介绍了单元测试的框架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); }
本篇到这,下篇再记录一些其它用法。
下一篇: 可执行Jar包运行原理
推荐阅读
-
.NET Core实战项目之CMS 第十章 设计篇-系统开发框架设计
-
.NET Core 迁移躺坑记续集之Win下莫名其妙的超时
-
asp.net core 系列之并发冲突的深入理解
-
abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理三 (二十一)
-
abp(net core)+easyui+efcore实现仓储管理系统——ABP WebAPI与EasyUI结合增删改查之五(三十一)
-
abp(net core)+easyui+efcore实现仓储管理系统——ABP WebAPI与EasyUI结合增删改查之八(三十四)
-
ASP.NET Core使用GraphQL第一章之Hello World
-
Asp.Net Core 单元测试正确姿势
-
Asp.Net Core 单元测试正确姿势
-
abp(net core)+easyui+efcore实现仓储管理系统——EasyUI之货物管理五 (二十三)