天天看點

單元測試與解耦

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.

繼續閱讀