如何讓代碼可測試化(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;

        }

    }

 

暫時先寫到這里,呵呵... 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM