.NET 單元測試(二):入門

基於狀態測試

  在上一篇文章中,咱們舉了一個帶返回值的例子,那麼無返回值的狀況下又該怎樣寫單元測試呢?html

有以下代碼:web

public IList<string> Names = new List<string>();
  
public void Reset()
{
    Names.Clear();
}
複製代碼

咱們發現,Reset方法內部執行的是Names列表的清空操做,這個操做能夠抽象成對被測試類狀態的更改,要驗證狀態更改是否符合預期,咱們只須要驗證更改先後是否符合預期便可。在這裏,只須要測試Reset方法是否按照咱們預期的把Names清空便可。以下:bash

/// <summary>
/// 條件:Names不爲空
/// 預期:清空Names
/// </summary>
[TestMethod()]
public void ResetTest_NamesNotEmpty_NamesEmpty()
{
   //Arrange
   var document = new Document();
   document.Names.Add("name0");
   document.Names.Add("name1");

   //Action
   document.Reset();

   //Assert
   Assert.AreEqual(document.Names.Count, 0);
}
複製代碼

依賴外部對象的測試

  單元測試須要可以快速獨立運行,隔離掉對外部的依賴是很是必要的,好比文件系統、硬件數據、web服務等。ide

以下代碼:函數

///<summary>
/// 判斷當前字符串是不是合法的html字符串
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public bool IsValidHtml(string input)
{
    var textService = new TextService();
 
    return textService.IsValidHtml(input);
}
複製代碼

能夠看到,當前方法依賴TextService來驗證html,可是在運行單元測試時,TextService的狀態是未知的,它甚至可能還未開發完成。所以,須要隔離掉對TextService的依賴。單元測試

而TextService是在IsValidHtml方法內部建立的,咱們沒法隔離,這個時候就須要對方法進行一系列的修改,以使得它達到可測試的要求(這就是所謂的單元測試約束設計)。測試

再進一步的分析,能夠發現依賴的是TextService提供的IsValidHtml()方法,而並不是TextService這個對象,這就好說了,讓IsValidHtml()依賴能夠提供html驗證的接口,咱們就能夠不用依賴TextService這個對象了,咱們抽取接口:優化

public interface ITextService
{
    bool IsValidHtml(string input);
}
複製代碼

這樣咱們就能夠從對具體實現的依賴解耦爲對接口的依賴,所以,在測試方法中咱們能夠很方便的用一個假的ITextService的實現來替代真實的TextService,由此隔離對真實外部服務的依賴。ui

這個假的ITextService的實現咱們稱爲 僞對象spa

以下SubTextService就是咱們的僞對象:

public class SubTextService : ITextService
{
    private bool _isValidHtml;
 
    public void SetIsValidHtml(bool value)
    {
        _isValidHtml = value;
    }
 
    public bool IsValidHtml(string input)
    {
        return _isValidHtml;
    }
}
複製代碼

有了僞對象,怎麼使用起來呢?

接下來介紹幾種僞對象注入的方式

  • 構造函數注入

    這種方式須要被測試類提供一個帶有ITextService參數的構造函數,咱們修改被測試類:

    public Document(ITextService textService)
    {
        _textService = textService;
    }
    
    /// <summary>
    /// 判斷當前字符串是不是合法的html字符串
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public bool IsValidHtml(string input)
    {
        return _textService.IsValidHtml(input);
    }
    複製代碼

    接下來,在測試方法中就能夠將僞對象注入進去了:

    /// <summary>
    /// 條件:傳入Empty的字符串
    /// 預期:返回False
    /// </summary>
    [TestMethod()]
    public void IsValidHtml_EmptyInput_ReturnFalse()
    {
        //Arrange
        var subTextService = new SubTextService();
        subTextService.SetIsValidHtml(false);
        var document = new Document(subTextService);
    
        //Action
        var result = document.IsValidHtml(string.Empty);
    
        //Assert
        Assert.IsFalse(result);
    }
    複製代碼

    這種方法比較簡單,被測試類的代碼改動也不大。 可是,若是方法中依賴多個外部接口,須要構造函數的參數列表可能很長;或者被測試類中不一樣方法依賴了不一樣的外部接口,那麼須要增長多個構造函數。 所以,此方法須要根據狀況謹慎使用。

  • 屬性注入

    這種方式指的是被測試類將外部接口的依賴設計成能夠公開屬性:

    public ITextService TextService { get; set; }
    複製代碼

    這樣在單元測試中就能夠方便的將僞對象注入進去。 這種方法簡單,對被測試類改動小。 可是,將TextService設計成屬性,會給外部一種TextService的賦值非必需的誤解,然而在咱們的設計中TextService是必須的。 所以,不推薦使用。

  • 工廠注入

    工廠注入指的是當咱們依賴的第三方接口是用工廠新建時,經過給工廠中注入僞對象來隔離對真實對象的依賴。

    public static class TextServiceFactory
    {
        private static ITextService _textService = new TextService();
    
        public static ITextService Create()
        {
            return _textService;
        }
    
        public static void SetTextService(ITextService textService)
        {
            _textService = textService;
        }
    }
    複製代碼

    這種方法也比較簡單,須要對工廠方法進行修改,改動量也不大。 可根據狀況使用。

  • 派生類注入

    派生類注入指的是在設計的時候,把對外部的依賴對象的獲取設計成能夠被繼承,這樣僞對象就能夠在不修改原來代碼的狀況下完成注入:

    protected virtual ITextService GetTextService()
    {
        return new TextService();
    }
    複製代碼

    寫單元測試的時候,只須要用僞對象繼承被測試類,就能夠在重寫GetTextService時,注入僞對象。

    //Document爲被測試類
    public class SubDocument : Document
    {
        protected override ITextService GetTextService()
        {
            return new SubTextService();
        }
    }
    複製代碼

    在單元測試時,就直接使用SubDocument便可. 這種方法比較簡單,並且不須要修改被測試類代碼。 推薦此方法。

  寫單元測試能夠爲咱們的代碼增長一層保護,在設計程序時考慮單元測試也能夠優化咱們的設計,好處多多,何樂而不爲呢(●'◡'●)

2017-3-17 23:29:07
相關文章
相關標籤/搜索