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