為什麼要寫一個DateTime的Stub呢?
在有些情況必需判斷今天,如不同的節日,不同的Logo,因為System.DateTime.Now或Today,沒有辦法模擬,寫Unit Test時就沒有辦法測各個時間點的反應,總不可能做個測試要改系統時間吧。
建立DateTImeProdiver的虛擬類別,來包裝DateTime,有二個實作DefaultDateTimeProdiver是一般的Code使用,TestDateTimeProvider是Test的Code使用。
圖一 DateTiemProdiver的類別圖
使用
所有呼叫System.DateTime.Now或Today,都改成用System.DateTimeProdiver.Current.Now或Today,為了方便測試,改變一點寫法不為過吧。
DateTime today = DateTimeProvider.Current.Today;
測試時
//測試時改變DateTimeProvider的實例,以便摸擬不同時間的變化 DateTimeProvider.Current = new TestDateTimeProvider(new DateTime(2010, 1, 1)); Assert.AreEqual(LogoHelper.GetLogo(),"元旦.png"); DateTimeProvider.Current = new TestDateTimeProvider(new DateTime(2010, 2, 14)); Assert.AreEqual(LogoHelper.GetLogo(), "情人節.png");
原始碼
DateTImeProdiver與DefaultDateTimeProdiver
//使用System,方便呼叫 namespace System { /// <summary> /// 為了單元測試增加的提供者,如DateTime.Now,無法Mock,所以多一層以便測試 /// </summary> public abstract class DateTimeProvider { /// <summary> /// 預設的DateTimeProvider /// </summary> private class DefaultDateTimeProvider : DateTimeProvider { /// <summary> /// 使用獨體模式 /// </summary> private static DateTimeProvider instance; static DefaultDateTimeProvider() { instance = new DefaultDateTimeProvider(); } public static DateTimeProvider Instance { get { return instance; } } public override DateTime Now { get { return DateTime.Now; } } public override DateTime UtcNow { get { return DateTime.UtcNow; } } public override DateTime Today { get { return DateTime.Today; } } } /// <summary> /// 預設使用DefaultDateTimeProvider的獨體值 /// </summary> private static DateTimeProvider current = DefaultDateTimeProvider.Instance; /// <summary> /// 可替換成別的DateTimeProvider /// </summary> public static DateTimeProvider Current { get { return DateTimeProvider.current; } set { if (value == null) { throw new ArgumentNullException("value"); } DateTimeProvider.current = value; } } public abstract DateTime Now { get; } public abstract DateTime UtcNow { get; } public abstract DateTime Today { get; } } }
TestDateTImeProdiver
/// <summary> /// 測試專用的DateTimeProvider /// </summary> public class TestDateTimeProvider : DateTimeProvider { private DateTime datetime; public TestDateTimeProvider(DateTime datetime) { this.datetime = datetime; } public override DateTime Now { get { return datetime; } } public override DateTime UtcNow { get { return datetime.ToUniversalTime(); } } public override DateTime Today { get { return datetime.Date; } } }