在寫程式時,我們很常用 DateTime.Now 來取得現在的時間。但這樣會遇到一個問題:若要為該方法寫測試時,會因為使用 DateTime.Now,每次取得的時間都不同,導致測試無法通過。
現在有個方法單純的回傳字串,但因為 timeNow 是不固定的,導致測試無法通過,這邊介紹兩種方法,都可以解決此問題。
1 2 3 4 5 6 7 public string CreateMessage ( ) { DateTime timeNow = DateTime.Now; string result = "Time now is " + timeNow.ToString(); return result; }
1. 用抽象方法取得現在時間 我們可以用一個抽象方法取得現在時間,寫測試時只要複寫該方法,就能讓取得時間為固定值。這樣做會需要用 StubTimeLogger 去繼承 TimeLogger,並覆寫抽象方法 GetTimeNow。
TimeLogger.cs: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using System;namespace Test.UnitTest.SystemTime { public class TimeLogger { public TimeLogger ( ) { } public string CreateMessage ( ) { DateTime timeNow = this .GetTimeNow(); string result = $"Time now is {timeNow:yyyy/MM/dd H:mm:ss} " ; return result; } public virtual DateTime GetTimeNow ( ) { return DateTime.Now; } } }
TimeLoggerTest.cs: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 using Microsoft.VisualStudio.TestTools.UnitTesting;using System;namespace Test.UnitTest.SystemTime { [TestClass ] public class TimeLoggerTest { public TimeLoggerTest ( ) { } [TestMethod ] public void 印出現在時間_使用StubTimeLogger() { DateTime dateTime = new DateTime(2021 , 6 , 6 , 13 , 13 , 13 ); StubTimeLogger timeLogger = new StubTimeLogger(dateTime); var actual = timeLogger.CreateMessage(); var expected = "Time now is 2021/06/06 13:13:13" ; Assert.AreEqual(actual, expected); } public class StubTimeLogger : TimeLogger { private DateTime _dateTime; public StubTimeLogger (DateTime dateTime ) { this ._dateTime = dateTime; } public override DateTime GetTimeNow ( ) { return this ._dateTime; } } } }
2. 建立 SystemTime 類別來取得時間 使用 SystemTime 類別來取得時間,這種寫法就不用建立一個 StubTimeLogger 再複寫抽象方法。
TimeLoggerUsingSystemTime.cs: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using Microsoft.VisualStudio.TestTools.UnitTesting;using System;namespace Test.UnitTest.SystemTime { public class TimeLoggerUsingSystemTime { private SystemTime _systemTime; public TimeLoggerUsingSystemTime (SystemTime systemTime ) { this ._systemTime = systemTime; } public string CreateMessage_SystemTime ( ) { DateTime timeNow = this ._systemTime.Now; string result = $"Time now is {timeNow:yyyy/MM/dd H:mm:ss} " ; return result; } } }
SystemTime.cs: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 using System;namespace Test.UnitTest.SystemTime { public class SystemTime { private DateTime _date; public SystemTime ( ) { } public void Set (DateTime custom ) { this ._date = custom; } public void Reset ( ) { this ._date = DateTime.MinValue; } public DateTime Now { get { if (this ._date != DateTime.MinValue) { return this ._date; } return DateTime.Now; } } } }
TimeLoggerUsingSystemTimeTest.cs: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using Microsoft.VisualStudio.TestTools.UnitTesting;using System;namespace Test.UnitTest.SystemTime { [TestClass ] public class TimeLoggerUsingSystemTimeTest { public TimeLoggerUsingSystemTimeTest ( ) { } [TestMethod ] public void 印出現在時間_使用SystemTime() { SystemTime systemTime = new SystemTime(); DateTime customDateTime = new DateTime(2021 , 6 , 6 , 13 , 13 , 13 ); systemTime.Set(customDateTime); TimeLoggerUsingSystemTime timeLogger = new TimeLoggerUsingSystemTime(systemTime); var actual = timeLogger.CreateMessage_SystemTime(); var expected = "Time now is 2021/06/06 13:13:13" ; Assert.AreEqual(expected, actual); } } }
完整程式碼請參考
結論 今天介紹了兩種方法,以解決因為使用 DateTime.Now 而無法通過單元測試的問題,我個人是比較喜歡方法一,方法二需注意是否有適時的 Reset,否則可能會取得非預期的時間。
參考 Chapter 7 測試階層與組織.單元測試的藝術 第二版