背景

在目前对应的项目中,出现了后端部分功能无法在期限内正常开发完成,导致Web前端无法正常推进黑盒测试的情况。

上图为周会上客户侧的回复,提到了【スタブ開発】这个没见过的关键词。

查了一下日语,释义还算清楚:

スタブ(stub)とは、切り株、半券、(何かが減ったり短くなった)残り、などの意味を持つ英単語。

ITの分野では、本物が用意できないときに動作に支障が無いようにとりあえず置いておく代用品という意味で用いられることが多い。

スタブ(stub) 是一个有多种含义的英语单词,通常指切株、半券、或是指某物减少或缩短后的残留部分等。

在信息技术(IT)领域,"スタブ" 常用来指代当无法提供真实功能或组件时,为了不影响操作而临时放置的替代品。


什么是 Stub

Stub 是计算机科学中一个重要的概念,广泛应用于分布式系统、软件开发与测试

Stub 是一种编程技术,主要用于软件系统中模拟或代替真实组件以满足特定需求。“Stub”这个词在编程中的使用可以追溯到早期的软件开发实践。它的概念源自于早期的程序设计中,开发者需要一种方法来模拟尚未实现的功能,以便进行测试和集成。这种做法逐渐演变为一种常见的开发和测试技术。

通俗来说,Stub 是一个占位符或替代物,在运行时被用来模拟真实组件的行为。

从本质上讲,Stub 是一种轻量级的实现,通常在以下场景中使用:

  • 系统开发初期,真实组件尚未开发完成时。

stub 可以用来快速实现一个功能的外壳,以便其他部分的代码能够正常运行,而无需等待实际功能的开发完成。

  • 测试过程中,真实组件难以直接调用或会产生副作用时。

stub 可以模拟一个函数或方法的行为,返回预定义的结果,以便测试其他部分的代码。这有助于隔离测试,确保测试的准确性和可靠性。

  • 分布式系统中,用于解决不同组件之间通信延迟的问题。

栗子

假设正在开发一个系统,其中一个模块需要调用一个外部服务。

在实际服务尚未开发完成时,就可以使用一个 stub 来模拟这个服务的行为:

def get_user_info(user_id):
    # 这是一个 stub,模拟外部服务的行为
    return {
        "user_id": user_id,
        "name": "John Doe",
        "email": "john.doe@example.com"
    }

# 在测试中使用这个 stub
def test_get_user_info():
    user_info = get_user_info(123)
    assert user_info["user_id"] == 123
    assert user_info["name"] == "John Doe"
    assert user_info["email"] == "john.doe@example.com"

在这个例子中,get_user_info 函数是一个 stub,它返回预定义的用户信息,以便测试其他部分的代码。


再例如,测试一个购物车模块时,可能涉及与支付系统交互的逻辑。由于支付系统真实调用会涉及真实资金操作,可以用 Stub 来替代:

Stub 模拟支付系统的接口。

返回预定义的结果,例如支付成功失败

测试可以专注于验证购物车模块的逻辑,而无需担心支付系统的复杂性。


什么是Mock

Mock,主要是指某个程序的傀儡,也即一个虚假的程序,可以按照测试者的意愿做出响应,返回被测对象需要得到的信息。也即是要风得风、要雨得雨、要返回什么值就返回什么值。

一个闹钟根据时间来进行提醒服务,如果过了下午5点钟就播放音频文件提醒大家下班了,如果我们要利用真实的对象来测试的话就只能苦苦等到下午五点,然后把耳朵放在音箱旁,我们应该利用mock对象来进行测试,这样我们就可以模拟控制时间了,而不用苦苦等待时钟转到下午5点钟了。下面是代码:

public abstract class Environmental{
    boolean playedWav=false;
    public abstract  long getTime();
    public abstract  void  playWavFile(String fileName);
    public abstract  boolean  wavWasPlayed();
    public abstract  void  resetWav();
}
#真实的实现代码
public class SystemEnvironment extends Environmental{
    public long getTime(){
        return System.currentTimeMillis();
    }
    public void playWavFile(String  fileName){
        playedWav=true;
    }
    public boolean  wavWasPlayed(){
        return playedWav;
    }
    public void  resetWav(){
        playedWav=false;
    }
}
#mock对象
public class MockSystemEnvironment extends Environmental{
    private long currentTime;
    public long getTime(){
        return currentTime;
    }
    public void setTime(long  currentTime){
        this.currentTime=currentTime;
    }
    public void playWavFile(String  fileName){
        playedWav=true;
    }
    public boolean wavWasPlayed(){
        return playedWav;
    }
    public void  resetWav(){
        playedWav=false;
    }
}

栗子

一个虚拟对象,其中最初设置了某些属性。 此对象的行为取决于设置的属性。 也可以测试对象的行为。

例如,对于 Customer对象,你可以通过设置姓名和年龄来模拟它。你可以将年龄设置为 12,然后测试isAdult()方法,该方法将在大于 18 岁时返回 true。因此你的 Mock Customer 对象适用于指定的条件。

mock与stub的比较

1)mock和stub都是采用替换的方式来实现,被测试的函数中的依赖关系,不过mock采用的是接口替换的方式,stub采用的是函数替代的方式。

2)mock的实现对功能代码没有侵入性,stub的侵入性比较强,在实现功能函数的时候,就需要为了测试设置一些回调函数,也就是这里所谓的桩。

3)对于控制被替代的方法来讲,mock如果想支持不同的输出,就需要提前实现不同的分支代码,甚至需要定义不同的mock结构体来实现,这样的mock代码会变成一个支持所有逻辑分支的一个最大集合,mock代码复杂性会变高;stub却能很好的控制桩函数的不同分支,因为stub替换的是函数,那么只要需要再用到这种输出的时候,定义一个函数即可,而这个函数甚至都可以是匿名函数。

单元测试工具

easymock、jMock、Mockito、Unitils Mock、PowerMock、JMockit等

接口测试工具

Wiremock、Mockserver、Moco、Mock.js、RAP等

参考文章

https://www.cnblogs.com/longmo666/p/18428501

https://cloud.tencent.com/developer/news/2028774

https://cloud.tencent.com/developer/article/2352827