iT邦幫忙

2021 iThome 鐵人賽

DAY 13
1

假物件兄弟戰:虛設常式 V.S 模擬物件

相信許多人剛接觸完虛設常式與模擬物件,會說不出兩者之間確切的差別,有種曖昧糾纏的感覺。兩者都是假物件,之間最大的差異就在於是否有被驗證,我們來看張對照圖:

https://ithelp.ithome.com.tw/upload/images/20210913/20127378UP7hiNbg1p.png

從這張對照圖,可以看出左邊是在 Day-10 撰寫 Stub 時的流程圖,我們待測試的商業邏輯注入了虛擬常式後,呼叫商業邏輯的方法得到資料後,做為我們驗證的基準;另一方面,Day-12 撰寫 Mock 時,我們呼叫待測試的商業邏輯方法後,會去執行模擬物件的方法,因此,我們要驗證模擬物件是否真的有被執行。


看程式碼說故事 (Mock-2)

我們在撰寫單元測試時,很有可能存在許多假物件,有零或一個的模擬物件與一個以上的虛設常式;同時,也有可能在第一個單元測試擔任虛設常式,在第二個測試擔任模擬物件。接下來一樣,我們來用程式碼說故事,今天開發者又接到新的需求,要把先前所撰寫的 Email 通知系統與 Log 紀錄系統結合,發送完 Email 再紀錄 Log,程式碼如下:

public class EmailWithLogSystem
{
    private IEmailService EmailService;
    private ILogService LogService;

    public EmailWithLogSystem(IEmailService inEmailService, ILogService inLogService)
    {
        EmailService = inEmailService;
        LogService = inLogService;
    }

    public string SendFunction(string mailAddress, string mailMessage)
    {
        var SendResult = EmailService
                        .SendEmail(mailAddress, mailMessage);
        
        if (SendResult == "Fail") 
        {
            LogService.Log(mailAddress + " is not send yet!");
        }
        
        return SendResult;
    }
}

public interface IEmailService()
{
    public string SendEmail(string mailAddress, string mailMessage);
}

public interface ILogService()
{
    public string Log(string logMessage);
}

可以看出我們在呼叫 SendFunction 的時候,一開始會先呼叫 EmailService 的 SendEmail 方法,會回傳字串 SendResult,若發生失敗的情況則再中間會呼叫 LogService 的 Log 方法。

因此,單元測試可撰寫好幾種情況,SendEmail 成功與失敗的情況分別回傳的字串,以及失敗的時候是否會呼叫 Log 方法。

對此,測試 SendFunction 的策略,我們先測試失敗時是否會呼叫 Log 方法。首先,創建一個虛設常式代表 EmailService 做前置的過程,最後在呼叫 LogService 的 Log 時當做模擬物件,測試碼如下:

using NUnit3;

[TestFixture]
public class EmailWithLogSystemUnitTests
{
    [Test]
    public void SendFunction_CallLog_Success()
    {
        // Arrange
        StubEmailFailSerivce stubEmailService = new StubEmailFailSerivce();
        MockLogSerivce mockLogService = new MockLogSerivce();
        
        EmailWithLogSystem EmailWithLogService = new EmailWithLogSystem(stubEmailService, mockLogService);
        
        // Act
        var result = EmailWithLogService.SendFunction("Test@abc.com.tw", "Test Demo");
        
        // Assert
        Assert.AreEqual("Test@abc.com.tw is not send yet!", mockLogService.logMessage);
    }
}

public class StubEmailFailSerivce : IEmailService
{
    public string SendEmail(mailAddress, mailMessage)
    {
        return "Fail";
    }
}

public class MockLogSerivce : ILogService
{
    public string logMessage;
    
    public string Log(string LogMessage)
    {
        logMessage = LogMessage;
    }
}

OK,這樣撰寫完 StubEmailFailSerivce 成功扮演了虛設常式的工作,把商業邏輯中間的過程給處理掉;而 MockLogSerivce 則是擔任了模擬物件,做為測試方法 SendFunction_CallLog_Success 中,最後是否有成功產製 "Test@abc.com.tw is not send yet!" 這段文字。

那假設今天我們要看的不是 Log 方法是否有呼叫,而是 SendEmail 後是否有得到相對應的字串呢?那此時,在上隻擔任 MockLogSerivce 的任務則在這隻測試就變成 StubLogSerivce,測試碼如下:

using NUnit3;

[TestFixture]
public class EmailWithLogSystemUnitTests
{
    [Test]
    public void SendFunction_CatchSendResult_Success()
    {
        // Arrange
        StubEmailSuccessSerivce stubEmailService = new StubEmailSuccessSerivce();
        StubLogSerivce stubLogService = new StubLogSerivce();
        
        EmailWithLogSystem EmailWithLogService = new EmailWithLogSystem(stubEmailService, stubLogService);
        
        // Act
        var result = EmailWithLogService.SendFunction("Test@abc.com.tw", "Test Demo");
        
        // Assert
        Assert.AreEqual("Success", result);
    }
}

public class StubEmailSuccessSerivce : IEmailService
{
    public string SendEmail(mailAddress, mailMessage)
    {
        return "Success";
    }
}

public class StubLogSerivce : ILogService
{
    public string logMessage;
    
    public string Log(string LogMessage)
    {
        logMessage = LogMessage;
    }
}

有趣吧,一下擔任模擬物件、一下又擔任虛設常式,這也是單元測試中很吃靈感的地方。(我寫到這邊其實腦袋快燒乾了)/images/emoticon/emoticon06.gif

那接下來明天會進一步探討一些單元測試需要考量的情境,如只針對一個關注點測試、過度指定、假物件鏈等。


上一篇
Day 12-假物件 (Fake) - 模擬物件 (Mock)-1 (核心技術-4)
下一篇
Day 14-假物件 (Fake) - 模擬物件 (Mock)-3 (核心技術-6)
系列文
單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言