Day 12-假物件 (Fake) - 模拟物件 (Mock)-1 (核心技术-4)

模拟物件(Mock)简介

在先前我们所撰写的单元测试中,3A 原则所做的不外乎是新增物件、执行物件方法、验证物件回传的结果或呼叫物件本身的属性。好,关键在最後验证的时候,那如果今天他方法执行最後的不是回传值或修改物件本身的属性;而是呼叫另一个物件的方法呢?

相信看到这边还是有点抽象,那我们先来看一段程序码/images/emoticon/emoticon12.gif

using LLLog;

public class LogSystem
{
    public LogSystem()
    {

    }

    public void LogFunction(string LogMessage)
    {
        var LogService = new LLLogService();
        
        LogService.Log(LogMessage);
    }
}

我们从这段程序码可以看到执行 LogFunction 的时候,该方法最後呼叫 Log 方法时,并没有任何回传值,若用以前回传资料的验证方式,在此情况则无法处理。针对此情况,Roy Osherove 在单元测试的艺术中提出了互动测试 (Interaction Testing)的概念,其定义如下:

互动测试是针对一个物件如何向其他的物件发送讯息(呼叫方法)的测试。如果一个特定的工作单元的最终结果,是呼叫另外一个物件,你就需要进行互动测试。


相信如果第一次看段文字的人,应该很多人跟我一样一头雾水/images/emoticon/emoticon13.gif

但他在後来文中有提到一个例子还满清楚的解释互动测试的感觉,在这边写给各位做参考。假设今天有个灌溉系统,这个灌溉系统的目的是要给树浇水,每十二个小时浇一次水,我们要验证灌溉系统是否符合我们预期的结果有两种方式验证方式:

(1) 基於状态的整合测试:直接检查树的情况,这样的好处就是可以直接知道其状况,不会有测试结果与真实情况不符。但是其流程繁杂,且容易因为树的品种、环境等等,需要 Case by Case 设计。

(2) 互动测试:我们在浇水的水管末端,装上感测器,检测是否有在预期的时间浇水。换言之,我们不管树的情况,而是管「有没有浇水」,这样的好处就不会受树的品种、环境等等影响。


因此,接下来就是要探讨如何在撰写单元测试的时候如何把最後呼叫第三方套件的方法抽换成模拟物件,以下两张图分别是以前做资料验证的方式及今天接下来要讨论的验证。

https://ithelp.ithome.com.tw/upload/images/20210912/20127378O8UIhRVYcB.png
图 1、资料测试的流程

https://ithelp.ithome.com.tw/upload/images/20210912/20127378dv0gSdDLny.png
图 2、互动测试的流程


看程序码说故事 (Mock-1)

那接下来一样也用故事情境一步一步加深开发的情境与难度。

今天开发者接到新的需求,要开始撰写一只 Log 纪录系统,在法呼叫这只方法时,就会登记 Log 在相对应档案的记事本上,而先前已有前辈开发了一只套件叫 LLLog,所以他所需要做的事情也就是撰写好相对应的商业逻辑,如下:

介面设计:

public class LogSystem
{
    private ILogService LogService;
    
    public LogSystem(ILogService inLogService)
    {
        LogService = inLogService;
    }

    public void LogFunction(string LogMessage)
    {
        LogService.Log(LogMessage);
    }
}

介面实作:

using LLLog;

public class DemoLLLogSerivce : ILogService
{
    var LogService = new LLLogService();
    
    public string Log(string LogMessage)
    {
        LogService.Log(LogMessage);
    }
}

而针对测试的战术,因我们是要取代 LLLog 的 LogService.Log 方法;而该方法是没有回传值,但我们在测试时要有相对应的回传值,因此我们在模拟物件的类别中设定了属性,跑 Log 方法时把 Message 记录在 MockLogSerivce 的 logMessage,程序码如下:

using NUnit3;

[TestFixture]
public class LogSystemUnitTests
{
    [Test]
    public void LogFunction_Success()
    {
        // Arrange
        MockLogSerivce mockLogService = new MockLogSerivce();
        
        LogSystem LogService = new LogSystem(mockLogService);
        
        // Act
        LogService.Log("Test Demo");
        
        // Assert
        Assert.AreEqual("Test Demo", mockLogService.logMessage);
    }
}

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

如此以来,就解决没有回传值而无法验证的问题。


看起来好像理解了,但还是分不出虚设常式与模拟物件的差异

我们从上述的程序码中,简单诠释了模拟物件的概念;然而,什麽时候该用虚设常式,什麽时候要用模拟物件,两者搭配起来又有什麽样的情况,两者之间都出现在测试里面又要怎麽区分,都会在明天的文章中做个整理。


<<:  Day 12 让你的广告活动可以超乎预料的好

>>:  musl libc 简介与其 porting(二)Say hello to my little friend!

day11 : argo gitops服务以及ingress (上)

花了好几天终於完成了所有的基础设施,接着就可以开始部署服务以及使用了,对於k8s来说要部署服务需要的...

【Vue】v-text 、v-html与 {{ }} (Mustache)

【前言】 本系列为个人前端学习之路的学习笔记,在过往的学习过程中累积了很多笔记,如今想藉着IT邦帮忙...

Re: 新手让网页 act 起来: Day28 - Error boundary

前言 在开发的过程中,难免会有些疏失而导致程序出现错误,最终画面呈现一片空白。而在 React 中,...

Swift纯Code之旅 Day10. 「TableView(2) - TableView Cell注册」

前言 昨天已经将addAlarmContentTableViewCell的元件都建立完毕了,但是画面...

Day 28: Divide and Conquer

这是什麽 分而治之,分治法! 分治法的步骤是: 将一个问题拆解成多个可以处理的小问题後 处理、击破每...