博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何让代码可测试化(C#)
阅读量:5933 次
发布时间:2019-06-19

本文共 6910 字,大约阅读时间需要 23 分钟。

让代码可测试化

本篇介绍如何把我们目前最常见的代码转换为可以单元测试的代码,针对业务逻辑层来实现可测试性,我们以银行转账为例,通常代码如下:

public class TransferController

    {

        private TransferDAL dal = new TransferDAL();

        public bool TransferMoney(string fromAccount, string toAccount, decimal money)

        {

            //验证:比如账号是否存在、账号中是否有足够的钱用来转账

            if (fromAccount == null || fromAccount.Trim().Length == 0)

                return false;

            if (toAccount == null || toAccount.Trim().Length == 0)

                return false;

            if (IsExistAccount(fromAccount))//检查from账号是否存在

                return false;

            if (IsAccountHasEnoughMoney(fromAccount))//检查from账号中是否有足够的钱用来转账

                return false;

 

            //更新数据库

            dal.TransferMoney(fromAccount, toAccount, money);

 

            //发送邮件

            EmailSender.SendEmail("aaa@aa.com", "xxxxxxxx", "yyyyyyyyyy");

 

            return true;

        }

 

        private bool IsAccountHasEnoughMoney(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

 

        private bool IsExistAccount(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

    }

 

相应sql语句如下:

public void TransferMoney(string fromAccount, string toAccount, decimal money)

        {

            string sql = @"

 

                                UPDATE Accounts SET Money=Money-@Money WHERE Account=@FromAccount

                                UPDATE Accounts SET Money=Money+@Money WHERE Account=@FromAccount

 

";

        }

 

 

扎眼一看,这转账操作的逻辑写在了sql语句中(没有弱化外部操作),这样就会导致对业务逻辑代码的不可测试性,因此需要重构转账的计算部分,改成如下:

public class TransferController

    {

        private TransferDAL dal = new TransferDAL();

        public bool TransferMoney(string fromAccount, string toAccount, decimal money)

        {

            //验证:比如账号是否存在、账号中是否有足够的钱用来转账

            if (fromAccount == null || fromAccount.Trim().Length == 0)

                return false;

            if (toAccount == null || toAccount.Trim().Length == 0)

                return false;

            if (IsExistAccount(fromAccount))//检查from账号是否存在

                return false;

            if (IsAccountHasEnoughMoney(fromAccount))//检查from账号中是否有足够的钱用来转账

                return false;

 

            //更新数据库

            using(TransactionScope ts=new TransactionScope())

            {

                dal.MinuseMoney(fromAccount, money);

                dal.PlusMoney(toAccount, money);

                ts.Complete();

            }

 

            //发送邮件

            EmailSender.SendEmail("aaa@aa.com", "xxxxxxxx", "yyyyyyyyyy");

 

            return true;

        }

 

        private bool IsAccountHasEnoughMoney(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

 

        private bool IsExistAccount(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

    }

 

相对于业务逻辑层来说,分析出外部接口有:邮件发送、数据访问对象,因此增加这2个接口到代码中,变成如下:

public class TransferController

    {

        private ITransferDAO dao = new TransferDAL();

        private IEmailSender emailSender=new XXXXXXXXXXXXXXX();//由于一般的email发送类都是static的,不能new,这部分先留着,等下一步解决

 

        public bool TransferMoney(string fromAccount, string toAccount, decimal money)

        {

            //验证:比如账号是否存在、账号中是否有足够的钱用来转账

            if (fromAccount == null || fromAccount.Trim().Length == 0)

                return false;

            if (toAccount == null || toAccount.Trim().Length == 0)

                return false;

            if (IsExistAccount(fromAccount))//检查from账号是否存在

                return false;

            if (IsAccountHasEnoughMoney(fromAccount))//检查from账号中是否有足够的钱用来转账

                return false;

 

            //更新数据库

            using(TransactionScope ts=new TransactionScope())

            {

                this.dao.MinuseMoney(fromAccount, money);

                this.dao.PlusMoney(toAccount, money);

                ts.Complete();

            }

 

            //发送邮件

            this.emailSender.SendEmail("aaa@aa.com", "xxxxxxxx", "yyyyyyyyyy");

 

            return true;

        }

 

        private bool IsAccountHasEnoughMoney(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

 

        private bool IsExistAccount(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

    }

 

但是此时的2个接口,实际系统运行过程中还是会强耦合2个具体类,还是不可测试,怎么办呢?利用构造函数注入:

public class TransferController

    {

        private ITransferDAO dao;

        private IEmailSender emailSender;

 

        public TransferController()//实际运行时可以用这个构造

        {

            dao = new TransferDAL();

            emailSender = new EmailSenderAgent();

        }

 

        public TransferController(ITransferDAO dao, IEmailSender emailSender)//测试时用这个构造注入Fake对象

        {

            this.dao = dao;

            this.emailSender = emailSender;

        }

 

        public bool TransferMoney(string fromAccount, string toAccount, decimal money)

        {

            //验证:比如账号是否存在、账号中是否有足够的钱用来转账

            if (fromAccount == null || fromAccount.Trim().Length == 0)

                return false;

            if (toAccount == null || toAccount.Trim().Length == 0)

                return false;

            if (IsExistAccount(fromAccount))//检查from账号是否存在

                return false;

            if (IsAccountHasEnoughMoney(fromAccount))//检查from账号中是否有足够的钱用来转账

                return false;

 

 

            //更新数据库

            using(TransactionScope ts=new TransactionScope())

            {

                this.dao.MinuseMoney(fromAccount, money);

                this.dao.PlusMoney(toAccount, money);

                ts.Complete();

            }

 

            //发送邮件

            this.emailSender.SendEmail("aaa@aa.com", "xxxxxxxx", "yyyyyyyyyy");

 

            return true;

        }

 

 

        private bool IsAccountHasEnoughMoney(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

 

        private bool IsExistAccount(string fromAccount)

        {

            throw new System.NotImplementedException();

        }

    }

 

终于,可以编写单元测试了,看下面:

class TransferMoneyTest

    {

        public void TransferMoney_Validate_FromAccount_Null_Test()

        {

            string fromAccount=null;

            string toAccount="bbbbbbbbbbbb";

            decimal money=100;

 

            TransferController ctl = new TransferController(null, null);//因为这个测试用不到这2个接口,所以用了null

            bool real= ctl.TransferMoney(fromAccount, toAccount, money);

 

            Assert.IsFalse(real);

        }

        public void TransferMoney_Validate_FromAccount_Empty_Test()

        {

            string fromAccount = "";

            string toAccount = "bbbbbbbbbbbb";

            decimal money = 100;

 

            TransferController ctl = new TransferController(null, null);//因为这个测试用不到这2个接口,所以用了null

            bool real = ctl.TransferMoney(fromAccount, toAccount, money);

 

            Assert.IsFalse(real);

        }

 

        public void TransferMoney_Validate_FromAccount_AllSpace_Test()

        {

            string fromAccount = "              ";

            string toAccount = "bbbbbbbbbbbb";

            decimal money = 100;

 

            TransferController ctl = new TransferController(null, null);//因为这个测试用不到这2个接口,所以用了null

            bool real = ctl.TransferMoney(fromAccount, toAccount, money);

 

            Assert.IsFalse(real);

        }

 

        public void TransferMoney_Validate_FromAccount_NotExist_Test()

        {

            string fromAccount = "11111111111111";

            string toAccount = "bbbbbbbbbbbb";

            decimal money = 100;

 

            ITransferDAO dao = new FakeTransferDAO_NullAccount();

 

            TransferController ctl = new TransferController(dao, null);//因为这个测试用不到IEmailSender接口,所以用了null

            bool real = ctl.TransferMoney(fromAccount, toAccount, money);

 

            Assert.IsFalse(real);

        }

 

        public void TransferMoney_Validate_FromAccount_NotEnoughMoney_Test()

        {

            string fromAccount = "11111111111111";

            string toAccount = "bbbbbbbbbbbb";

            decimal money = 100;

 

            ITransferDAO dao = new FakeTransferDAO_NotEnoughMoney();

 

            TransferController ctl = new TransferController(dao, null);//因为这个测试用不到IEmailSender接口,所以用了null

            bool real = ctl.TransferMoney(fromAccount, toAccount, money);

 

            Assert.IsFalse(real);

        }

    }

用到了如下2个Fake类

class FakeTransferDAO_NullAccount : ITransferDAO

    {

        public void MinuseMoney(string fromAccount, decimal money)

        {

            throw new NotImplementedException();

        }

 

        public void PlusMoney(string toAccount, decimal money)

        {

            throw new NotImplementedException();

        }

 

        public Account GetAccount(string accountId)

        {

            return null;

        }

    }

 

    class FakeTransferDAO_NotEnoughMoney: ITransferDAO

    {

        public void MinuseMoney(string fromAccount, decimal money)

        {

            throw new NotImplementedException();

        }

 

        public void PlusMoney(string toAccount, decimal money)

        {

            throw new NotImplementedException();

        }

 

        public Account GetAccount(string accountId)

        {

            Account account = new Account();

            account.Money = 20;

            return account;

        }

    }

 

暂时先写到这里,呵呵... 

转载地址:http://sbctx.baihongyu.com/

你可能感兴趣的文章
如何更愉快地使用rem —— 别说你懂CSS相对单位
查看>>
Flutter中管理路由栈的方法和应用
查看>>
Android MVP模式 简介
查看>>
Python并行编程
查看>>
Qt应用自动化系列教程-01快速入门
查看>>
『高级篇』docker之Python开发信息服务(11)
查看>>
Decoding之Json解析
查看>>
Docker 私仓建设 Registry + Portainer
查看>>
修饰符final和static浅析
查看>>
用小猪佩奇说明Javascript的原型和原型链
查看>>
优雅的构建 Android 项目之磁盘缓存(DiskLruCache)
查看>>
天下无难试之HashMap面试刁难大全
查看>>
[MetalKit]11-Ray-tracing-in-a-Swift-playground2射线追踪2
查看>>
蚂蚁金服面试经历-临场发挥
查看>>
消息总线系统高级技术要点深度认知-kafka 商业环境实战
查看>>
Spring Cloud 入门教程 - 搭建配置中心服务
查看>>
46. Permutations
查看>>
为提升应用品质助力,绿标2.0检测项技术详解
查看>>
React as a UI Runtime(五、列表)
查看>>
数据库索引融会贯通
查看>>