Testify Mock 单元测试

这篇具有很好参考价值的文章主要介绍了Testify Mock 单元测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Testify 提供了单测方便的断言能力,这里的断言是将对代码实际返回的断言,代码的实际输出和预期是否一致。下面是 gin-gonic/gin 代码库的单测代码,Testify 还提供了很多其他的方法:

assert.Equal(t, "admin", user)
assert.True(t, found)

单元测试中也会存在不稳定的代码,我们的入参虽然保持不变,但每次单测的结果可能会发生变化。比如说,我们会调用第三方的接口,而第三方的接口可能会发生变化。再比如,代码中有通过 time.Now() 获取最近7天内的用户订单,这个返回结果本身就是随当前时间变化的。

当然,我们肯定不希望每次单测都手动调整,来“迎合”这类不稳定的代码,这样不仅疲于奔命,还效率不高。测试上使用 Mock 就可以解决这类问题。这里主要看看如何使用 Testify 的 mock 功能,从下面这个简单的例子出发:

package main

import (
	"math/rand"
	"time"
)

func DivByRand(numerator int) int {
	rand.Seed(time.Now().Unix())
	return numerator / int(rand.Intn(10))
}

DivByRand 中除以一个随机数,导致结果是随机的,不可预测的,我们该如何对它进行单测呢?特别强调下,rand.Seed 方法调用是必须的,如果不随机初始化 seed,rand.Intn 每次返回的结果都是相同的。随机数都是基于某个 seed 的随机数,seed 不变,预期的随机数就是固定不变的。

我们针对随机方法的部分,做一个接口声明,以及接口实现,来替代代码中随机的被除数。这里mock需要对原函数做代码改造,我们一起来看一下调整过程:

import (
	"github.com/stretchr/testify/mock"
	"testing"
)

// 声明随机接口
type randNumberGenerator interface {
    randomInt(max int) int
}

// 声明接口的实现
type standardRand struct{}

func (s standardRand) randomInt(max int) int {
	rand.Seed(time.Now().Unix())
    return rand.Intn(max)
}

// 修改原有的方法,已经修改了原函数的声明
func DivByRand(numerator int, r randNumberGenerator) int {
	return numerator / r.randomInt(10)
}

使用 Testify mock 功能

我们声明一个 mock 结构体,匿名嵌套 mock.Mock。通过嵌套 Mock,结构体就具备了注册方法,返回预期结果的能力。其中,randomInt 的实现对应了我们预期的输入和输出关系。

type mockRand struct {
	mock.Mock
}

func newMockRand() *mockRand { return &mockRand{} }

func (m *mockRand) randomInt(max int) int {
	args := m.Called(max)
	return args.Int(0)
}

最终,我们的单测就变成了下面的样子,其中的 On 用来对结构体的方法 randomInt 做设置,Return 对应了 args.Int(0)。我们执行下面的单测,返回的结果是恒定的。

func TestDivByRand(t *testing.T) {
	m := newMockRand()
	m.On("randomInt", 10).Return(6)

	t.Log(DivByRand(6, m))
}

Testify Mock 方法实现的核心就是 On 和 Return 方法了,它对应的是我们接口的实现。m.Called 函数的返回值类型为 Arguments,包含了函数的返回值信息,args.Int(0) 表示获取 Called 函数的第一个返回值。On 用来给函数设置入参,Return 用来给函数设置出参。

randomInt 方法只返回了一个结果,一般的函数还会额外返回 error 信息,来标志函数执行是否出现异常。这种情况可以通过 args.Error(1) 来获取,表示第二个出参是 error 类型。

从起初的单测,到最后的单测,函数的方法声明被修改了。假设我们要处理的是线上代码,这样的改动其实破坏了代码的稳定性,为了单测,我们还需要重新对代码做回归测试,确保改动不会对线上环境产生影响。但这种改动也可以总结为一种模式,只要按照固定的模式做代码调整就可以了:

  1. 针对返回结果不确定的方法 ,封装独立的接口声明。
  2. 使用 Testify Mock 重新实现这个接口
  3. 使用 Testify Mock 确定性的实现来替代原来的方法。前提是将之前直接调用方法的地方,修改为接口调用。

所以,这种情况其实不利于存量代码的覆盖率测试,单测最好做到是不要侵入老代码,避免引入不必要风险。但在增量代码上,我们可以使用这种模式,提前按照接口的模式去做功能实现,也就是测试驱动的意思。

在开发代码之前,先想好单测的实现,代码设计上多了一个维度的考量,也会让代码写的更加有扩展性。

库的其他 mock 方法

观察下面的代码,它用到了更多 testify 库提供的方法,包括 MatchedBy、Once、AssertNumberOfCalls。不过就数 AssertNumberOfCalls 最简单了,用来断言方法的调用次数。

mock.MatchedBy(reqSlothFacts) 本来应该是传递函数 ListAnimalFacts 入参的,现在传递了 mock.MatchedBy 函数的执行结果。我们详细来看一下这几个方法。

func TestGetSlothsFavoriteSnackOnPage2(t *testing.T) {
    c := newMockClient()
    c.On("ListAnimalFacts", mock.MatchedBy(reqSlothFacts)).
        Return(&page1, nil).
        Once()
    c.On("ListAnimalFacts", mock.MatchedBy(reqSlothFacts)).
        Return(&page2, nil).
        Once()

    favSnack, err := getSlothsFavoriteSnack(c)
    if err != nil {
        t.Fatalf("got error getting sloths' favorite snack: %v", err)
    }

    if favSnack != "hibiscus flowers" {
        t.Errorf(
            "expected favorite snack to be hibiscus flowers, got %s",
            favSnack,
        )
    }

    c.AssertNumberOfCalls(t, "ListAnimalFacts", 2)
}

Once 和 sync.Once 要表示的含义是一致的,表示只能执行一次,但多次执行 sync.Once 也是没有问题的,只不过只有第一次生效而已。但 mock 中的 Once 执行两次是会报错的。如果只是用来限定方法的执行次数,想一想,也没啥好单测的。沿用上面的代码,我们稍微做些改动

func TestDivByRand(t *testing.T) {
	m := newMockRand()
	m.On("randomInt", 10).Return(6).Once()

	m.randomInt(10)
	m.randomInt(10)
}

执行上面的单测,会发生 panic,提示信息中声明了:assert: mock: The method has been called over 1 times.。就目前来说,难道除了 panic 就没有什么更好的方式了?

其实 Once 还有另一个特别有用的功能,就是设置函数不同的返回值,还拿 randomInt 函数来说,如果我们期望,同样的入参,第一次调用 randomInt 返回6, 第二次调用 randomInt 返回 5 怎么处理。注意,是相同的入参。我们可以通过这样的方式就能输出 6、5。

func TestDivByRand(t *testing.T) {
	m := newMockRand()
	m.On("randomInt", 10).Return(6).Once()
	m.On("randomInt", 10).Return(5).Once()

	t.Log(m.randomInt(10))
	t.Log(m.randomInt(10))
}

当然,如果不使用 Once,用下面这种不同的入参,也能达到相同的效果。改动点主要是 On 方法的第二个参数,以及 Return 方法的返回值。

func TestDivByRand(t *testing.T) {
	m := newMockRand()
	m.On("randomInt", 10).Return(6)
	m.On("randomInt", 9).Return(5)

	t.Log(m.randomInt(10))
	t.Log(m.randomInt(9))
}

最后,我们来看看 MatchedBy 的用法,可以用来校验方法的入参。这个方法的使用约束比较多,参数需要是一个函数,函数的返回只是 bool 类型,校验成功返回 true,校验失败返回 false。函数的入参就是方法的入参类型,且只能处理一个参数,如果方法有多个参数,需要声明多个 MatchedBy。

我们用例子来看一下,我们校验 randomInt 的参数必须等于10,如果不等于10,单测又会抛出 panic。整体来看,MatchedBy 的效用不是特别大。

func TestDivByRand(t *testing.T) {
	m := newMockRand()
	m.On("randomInt", mock.MatchedBy(func(num int) bool {
		return num == 10
	})).Return(6)

	t.Log(m.randomInt(10))
}

本文主要是参照 Mocks in Go tests with Testify Mock 的示例,不过原文章讲的过于详细文章来源地址https://www.toymoban.com/news/detail-438755.html

到了这里,关于Testify Mock 单元测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • mock写单元测试和查数据库的单元测试

    mock写单元测试和查数据库的单元测试

    一:mock方式 在测试类上添加注解 将需要测试的类bean添加进来,该类中的其他bean也添加进来 给被测试类中用到的参数、返回值类创建对象 创建BeforeEach和AfterEach方法,在BeforeEach方法中给参数,返回值设置值 然后在test方法中设置被测试的方法 二:可以检测dao层sql的单元测试

    2024年02月15日
    浏览(14)
  • Go 单元测试之mock接口测试

    Go 单元测试之mock接口测试

    目录 一、gomock 工具介绍 二、安装 三、使用 3.1 指定三个参数 3.2 使用命令为接口生成 mock 实现 3.3 使用make 命令封装处理mock 四、接口单元测试步骤 三、小黄书Service层单元测试 四、flags 五、打桩(stub) 参数 六、总结 6.1 测试用例定义 6.2 设计测试用例 6.3 执行测试用例代码

    2024年04月22日
    浏览(15)
  • Go语言测试——【单元测试 | Mock测试 | 基准测试】

    Go语言测试——【单元测试 | Mock测试 | 基准测试】

    作者 :非妃是公主 专栏 :《Golang》 博客主页 :https://blog.csdn.net/myf_666 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 软件测试 :软件测试(英语:Software Testing),描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。换句话说,软件测

    2024年02月10日
    浏览(10)
  • CompletableFuture的单元测试Mock

    CompletableFuture的单元测试Mock

    在spring项目,假设我们有一个方法 我们对这个方法单元测试,大概率就直接写成: 这样会导致Completable的线程不运行,一直阻塞在红色箭头指示的地方: 等待线程执行完毕。然而线程并没有执行。 此时需要模拟并驱动异步线程的执行,因此需要这样写: 这样就mock了对Runn

    2024年02月09日
    浏览(9)
  • SpringBoot 使用Mock单元测试

    SpringBoot 使用Mock单元测试

    测试一般分为两种黑盒测试和白盒测试。         黑盒测试又称为 功能测试 或 数据驱动测试 ,测试过程中,程序看作成一个黑色盒子,看不到盒子内部代码结构。         白盒测试又称为 结构测试 或 逻辑驱动测试 ,测试过程中,程序看作一个透明盒子,能够看清

    2024年03月20日
    浏览(13)
  • 单元测试之Power Mock

    一、简介 EasyMock、Mockito、jMock(单元测试模拟框架) 在有这些模拟框架之前,程序员为了编写某一个函数的单元测试,必须进行十分繁琐的初始化工作,以确保调用的接口或编写的代码得到预期的结果。单元测试模拟框架极大的简化了单元测试的编写过程,在被测试代码需要

    2023年04月08日
    浏览(6)
  • JUnit 5单元测试(二)—— 断言

    JUnit 5单元测试(二)—— 断言

    上一篇讲了 JUnit 5单元测试(一)—— 基本配置,书接上文开始 JUnit 5单元测试(二)—— 断言 1.单元测试的类名应该起为 xxxxTest.java 表明这个一个测试类,类名应该用简洁的英文表明测试内容或函数。 例如:为了测试一个计算和的方法,可以取名为 SumTest.java 2.测试方法上

    2024年01月16日
    浏览(11)
  • SpringBoot单元测试断言 assertions

    SpringBoot单元测试断言 assertions

    断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别: 1、简单断言 2、数组断言 通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

    2024年02月05日
    浏览(14)
  • java单元测试( Hamcrest 断言)

    java单元测试( Hamcrest 断言)

    单元测试特征: 1 范围狭窄 2 限于单一类或方法 3 体积小 为什么要编写单元测试? 为了防止错误(很明显!) 而且还可以提高开发人员的生产力,因为单元测试: (1) 帮助实施——在编码的同时编写测试可以快速反馈正在编写的代码。 (2) 失败时应该易于理解——每个测试在

    2024年02月06日
    浏览(12)
  • 【Junit】单元测试Mock静态方法

    【Junit】单元测试Mock静态方法

    开发依赖 版本 Spring Boot 3.0.6 JDK 20 如果没有引入 mockito-inline 这个依赖,使用mock静态方法,则会抛这个异常

    2024年02月04日
    浏览(14)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包