欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.net core 单元测试之 JustMock第一篇

程序员文章站 2022-06-29 21:21:38
前面介绍了单元测试的框架NUnit,它可以很好的帮助我们建立测试,检验我们的代码是否正确。但这还不够,有时候我们的业务比较重,会依赖其它的类。基于隔离测试的原则,我们不希望依赖的其它类影响到我们的测试目标。这时候Mock就显得十分重要了。当然还有其它因素使得我们必须Mock对象,比如配置文件,DB等 ......

前面介绍了单元测试的框架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 。

开启使用高级功能
.net core 单元测试之 JustMock第一篇

为什么需要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);
        }

本篇到这,下篇再记录一些其它用法。