如何让代码可测试化(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;

        }

    }

 

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

posted @ 2012-08-30 15:58  McKay  阅读(2758)  评论(8编辑  收藏  举报