• 如何让代码可测试化(C#)


    让代码可测试化

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

    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;

            }

        }

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

  • 相关阅读:
    fiddler查看IP地址和请求响应时间
    web安全测试排查
    搞站思路 <陆续完善中>
    sys模块进度条玩法笔记
    Webbench、ab命令:做压力测试的工具和性能的监控工具
    xlwings excel(三)
    xlwings excel(二)
    xlwings excel(一)
    xlwings API Documentation
    Python+Excel 操作对比
  • 原文地址:https://www.cnblogs.com/aarond/p/UnitTest.html
Copyright © 2020-2023  润新知