前景提要,昨天提到開發者要開發 Email 通知系統,於是他就利用 JJEmail 套件裡面的 SendEmail 方法解決 Email 通知系統裡面的寄信功能,並回傳寄信的情況(資料型別為一串字串);但在 Code Review 階段被質疑說測試出現問題時,無法判別結果失敗是因為程式碼撰寫有誤,還是因為套件出錯,此外,測試找不到適當的切入點撰寫假物件。
因此,今天要先解決商業邏輯程式碼透過依賴反轉的概念,設計介面並讓 JJEmail 用依賴注入的方式寫進邏輯,以下為目前的流程圖:
在單元測試的藝術中曾提到一個讓測試介入更簡單的觀點:任何物件導向的問題,都可以透過增加一個中介層(a layer of indirection)來解決;當然,除了中介層過多的這個問題
;換言之,從 C# 的角度看這段話,一個簡單又好用的做法就是在 EmailFunction 與 JJEmail 之間設立介面,程式碼如下:
public class EmailSystem
{
private IEmailService EmailService;
public EmailSystem(IEmailService inEmailService)
{
EmailService = inEmailService;
}
public string EmailFunction(string mailAddress, string mailMessage)
{
var SendResult = EmailService
.SendEmail(mailAddress, mailMessage);
return SendResult;
}
}
public interface IEmailService()
{
public string SendEmail(mailAddress, mailMessage);
}
利用建構函式的概念,已經將 EmailSystem 當中的 EmailFunction 這隻功能從 JJEmail 抽離,而若要實作該介面(如利用 JJEmail),程式碼範例如下:
using JJEmail;
public class DemoJJEmailSerivce : IEmailService
{
var EmailService = new JJEmailService();
public string SendEmail(mailAddress, mailMessage)
{
var EmailFinalResult = EmailServiece
.SendEmail(mailAddress, mailMessage);
return EmailFinalResult;
}
}
而此時的流程圖已改成如下:
OK,介面抽離的部分終於處理完了,可以到這次的重點:虛設常式 (Stub),以這個例子,虛設常式要做的事情就是把 IEmailService 的功能實作出來並執行 EmailFunction 是否有誤。所以,測試碼的撰寫如下:
using NUnit3;
[TestFixture]
public class EmailSystemUnitTests
{
[Test]
public void EmailFunction_Success()
{
// Arrange
StubEmailSerivce stubEmailService = new StubEmailSerivce();
EmailSystem EmailService = new EmailSystem(stubEmailService);
// Act
var stubResult = EmailSystem
.EmailFunction("Test@abc.com.tw", "Test Demo");
// Assert
Assert.AreEqual("Success", stubResult);
}
}
public class StubEmailSerivce : IEmailService
{
public string SendEmail(EmailAddress, EmailMessage)
{
return "Success";
}
}
好,看到這邊,會注意到 StubEmailService 把 DemoJJEmailSerivce 的實作給替換掉,換成一隻不管參數是什麼,都一定會回傳 "Success" 的功能;但是,如果到這邊就結束,其實有點在做白工,因為特定寫了一個百分之百會回傳 "Success" 的方法,然後再呼叫這隻方法驗證是否其正確,在實務上是一件很奇怪的事情。
所以接下來明天會再針對這部分在做進一步探討,探討如果 EmailFunction 邏輯再擴充,對於測試上的影響,以及 Stub 真正的用意是什麼。