1.標題是什么意思?
1.1什么是單元測試?
單元測試,目的是為了保證代碼的質量;
1.2什么是解耦?
解耦,目的是為了方便單元測試。當然,另一個目的是為了保持程序的擴展性。
思想工具:為了同時達到單元測試與代碼解耦(或者稱為設計優良的OO代碼),那么依賴注入的思想是必不可少的工具。
- 之所以說是思想,從設計的角度來說,這確實是需要思想上的超越;
- 之所以說是工具,是因為有許多工具可以實現這一思想,如Ninject,Unity。
簡要如下圖所示:
2.解除外部依賴及實踐
外部依賴:配置文件、WS、數據庫、IO等,可控性較差,是集成測試的接縫點。
為什么要解除外部依賴,對於一個函數來說,只關注某一功能(即SRP,除非你想把所有的事情在一個方法內做完,但這不是OO,也沒有討論的價值)。
2.1.耦合的代碼
Eg:調用一個Web服務,最后發送郵件,但如果郵件服務掛了,剩余的邏輯就無法判斷了,所以,郵件服務是一個外部依賴,要模擬,或者打樁。
代碼清單1:——常規耦合的代碼
public class WebService { public void KaoQinSign(string userName,string from, string to) { //check the argument //validate // do something logistic MailHelper.SendMail("some content", from, to); } } public class MailHelper { public static void SendMail(string content, string from, string to) { //... } }
對於上述的KaoQinSign方法的寫法,常見,但不易測試,嚴格來說,在TDD開發中是不容出現的,根本原因是靜態方法的存在,阻止了可測試性,當然,對於Wrapper模式就另當別論。
考慮一個問題:如果KaoQinSign執行到MailHelper.SendMail方法,但運行時郵件系統不知道出了什么問題,異常了。
我通常的做法:將焦點轉移到了MailHelper.SendMail方法,修復之后,然后再回到KaoQinSign進行調試,如果MailHelper.SendMail有問題,繼續往前。——隨着方法調用的層次越來越深,焦點轉移的次數越來越遠,Bug率會很高。一般來說,調試的成功率和工作經驗成反比。
2.2 接口注入
代碼清單2:使用接口注入來解耦
public class WebService { public void KaoQinSign(IMail mail,string userName, string from, string to) { //check the argument //validate // do something logistic mail.SendMail("some content", from, to); } } public interface IMail { void SendMail(string content, string from, string to); } public class MailStub : IMail { public void SendMail(string content, string from, string to) { } }
上述代碼,解除了對外部郵件系統的依賴,使KaoQinSign具有可測試性,如果對模擬框架有所了解,那么使用Moq就可以輕松地模擬一個IMail接口,從而使代碼開發和測試能夠一路向前。
2.3 模擬與測試
代碼清單3:使用模擬框架進行方法的測試
[TestFixture] public class WebServiceTests { [Test] public void Method1_When_Exception_Will_SendMail() { WebService ws = new WebService(); //模擬郵件服務 Moq.Mock<IMail> mockMail = new Moq.Mock<IMail>(); //Verifiable表示:將要驗證SendMail是否被調用 mockMail.Setup(zw => zw.SendMail(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Verifiable(); ws.KaoQinSign(mockMail.Object,,"5299530", "One", "Other"); //驗證是否被調用 mockMail.Verify(); } }
代碼清單3,模擬郵件服務意思是這個服務是假的,用來確保這個方法通過的。
總結
單元測試的目的——確保(專業點來說稱為斷言)某一分支能夠正確地執行。
耦合的常見:
- 靜態方法;
- 一個函數干了幾百件事情;
- 一個函數內容有幾百個流程。
而解除外部依賴是常用的OO編碼方法。而上述的靜態方法就是典型的,符合二八定律。
使用
- SRP確保函數功能的唯一性;
- 針對接口編程(IOC);
- 使編碼具有可測試性(提取接口以及依賴注入)
才是TDD的最佳實踐,同時,TDD是開發能夠有效地橫向覆蓋(BFS式前進),而不需要使用DFS式地向前開發、調試,從而避免了DFS帶來的大腦爆棧。~~~ come from hp.

