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

基于狀態(tài)測試

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

有如下代碼:

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

我們發(fā)現(xiàn),Reset方法內部執(zhí)行的是Names列表的清空操作,這個操作可以抽象成對被測試類狀態(tài)的更改,要驗證狀態(tài)更改是否符合預期,我們只需要驗證更改前后是否符合預期即可。在這里,只需要測試Reset方法是否按照我們預期的把Names清空即可。如下:

/// <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);
}

依賴外部對象的測試

??單元測試需要能夠快速獨立運行,隔離掉對外部的依賴是非常必要的,比如文件系統(tǒng)、硬件數(shù)據(jù)、web服務等。

如下代碼:

///<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的狀態(tài)是未知的,它甚至可能還未開發(fā)完成。因此,需要隔離掉對TextService的依賴。

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

再進一步的分析,可以發(fā)現(xiàn)依賴的是TextService提供的IsValidHtml()方法,而并非TextService這個對象,這就好說了,讓IsValidHtml()依賴可以提供html驗證的接口,我們就可以不用依賴TextService這個對象了,我們抽取接口:

public interface ITextService
{
    bool IsValidHtml(string input);
}

這樣我們就可以從對具體實現(xiàn)的依賴解耦為對接口的依賴,因此,在測試方法中我們可以很方便的用一個假的ITextService的實現(xiàn)來替代真實的TextService,由此隔離對真實外部服務的依賴。

這個假的ITextService的實現(xiàn)我們稱為 偽對象。

如下SubTextService就是我們的偽對象:

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

有了偽對象,怎么使用起來呢?

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

  • 構造函數(shù)注入

    這種方式需要被測試類提供一個帶有ITextService參數(shù)的構造函數(shù),我們修改被測試類:
    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);
    }
    
    這種方法比較簡單,被測試類的代碼改動也不大。
    但是,如果方法中依賴多個外部接口,需要構造函數(shù)的參數(shù)列表可能很長;或者被測試類中不同方法依賴了不同的外部接口,那么需要增加多個構造函數(shù)。
    因此,此方法需要根據(jù)情況謹慎使用。
  • 屬性注入

    這種方式指的是被測試類將外部接口的依賴設計成可以公開屬性:
    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;
        }
    }
    
    這種方法也比較簡單,需要對工廠方法進行修改,改動量也不大。
    可根據(jù)情況使用。
  • 派生類注入

    派生類注入指的是在設計的時候,把對外部的依賴對象的獲取設計成可以被繼承,這樣偽對象就可以在不修改原來代碼的情況下完成注入:
    protected virtual ITextService GetTextService()
    {
        return new TextService();
    }
    
    寫單元測試的時候,只需要用偽對象繼承被測試類,就可以在重寫GetTextService時,注入偽對象。
    //Document為被測試類
    public class SubDocument : Document
    {
        protected override ITextService GetTextService()
        {
            return new SubTextService();
        }
    }
    
    在單元測試時,就直接使用SubDocument即可.
    這種方法比較簡單,而且不需要修改被測試類代碼。
    推薦此方法。

??寫單元測試可以為我們的代碼增加一層保護,在設計程序時考慮單元測試也可以優(yōu)化我們的設計,好處多多,何樂而不為呢(●'?'●)



2017-3-17 23:29:07

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,511評論 19 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,628評論 18 399
  • 我們以神的名義 進行一場無關緊要的愛情 只是快樂常常被忘記 悲傷也難以想起 忍住不說的言語 隨著時光逝去 在頂峰的...
    洛家仁人閱讀 181評論 0 0
  • 到了該交文章的最后期限了,可是我還在試圖找靈感,希望能做到字如泉涌般的自然流露出來。越是刻意,越是沒有感覺。...
    布二閱讀 183評論 0 0
  • 目的:實現(xiàn)自定義的上下拉刷新動畫,盡可能少的代碼侵入性。 輪子:MJRefresh 大多數(shù)方案:繼承MJRefre...
    ccSundayChina閱讀 1,523評論 3 14

友情鏈接更多精彩內容