• 101. Go单测系列1---使用monkey打桩


    本文将介绍如何在单元测试中使用monkey进行打桩。

    monkey支持为任意函数及方法进行打桩。

    monkey介绍

    monkey是一个Go单元测试中十分常用的打桩工具,它在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现,其原理类似于热补丁。

    monkey库很强大,但是使用时需注意以下事项:

    第一点:monkey不是线程安全的,所以不要把它用到并发的单元测试中。
    第二点: monkey不支持内联函数,在测试的时候需要通过命令行参数-gcflags=-l关闭Go语言的内联优化。执行单测时需要关闭内联优化,这样可以保证mock成功!!!

    如何禁用内联和编译优化

    命令行跑单测可以采用:

    go test -gcflags="all=-l -N" -v ./...
    
    • 1

    goland 图形界面可以采用:

    Debug 模式下跑单个测试时会自动带上该参数,Run 模式下跑单个测试或者跑一个包的测试则需要手动带上该参数

    在这里插入图片描述

    安装

    go get bou.ke/monkey
    
    • 1

    使用示例

    假设你们公司中台提供了一个用户中心的库varys,使用这个库可以很方便的根据uid获取用户相关信息。但是当你编写代码的时候这个库还没实现,或者这个库要经过内网请求但你现在没这能力,这个时候要为MyFunc(MyFunc中依赖了varys库中相关方法)编写单元测试,就需要做一些mock工作。

    // func.go
    
    func MyFunc(uid int64)string{
    	u, err := varys.GetInfoByUID(uid)
    	if err != nil {
    		return "welcome"
    	}
    
    	// 这里是一些逻辑代码...
    
    	return fmt.Sprintf("hello %s\n", u.Name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们使用monkey库对varys.GetInfoByUID进行打桩。

    // func_test.go
    
    func TestMyFunc(t *testing.T) {
    	// 对 varys.GetInfoByUID 进行打桩,跳转到我们指定的函数
    	// 无论传入的uid是多少,都返回 &varys.UserInfo{Name: "lym"}, nil
    	monkey.Patch(varys.GetInfoByUID, func(int64)(*varys.UserInfo, error) {
    		return &varys.UserInfo{Name: "lym"}, nil
    	})
    
    	ret := MyFunc(123)
    	if !strings.Contains(ret, "lym"){
    		t.Fatal()
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行单元测试:

    注意:这里为防止内联优化添加了-gcflags=-l参数。

    go test -run=TestMyFunc -v -gcflags=-l
    
    • 1

    输出:

    === RUN   TestMyFunc
    --- PASS: TestMyFunc (0.00s)
    PASS
    ok      monkey_demo     0.009s
    
    • 1
    • 2
    • 3
    • 4

    除了对函数进行mock外,monkey也支持对方法进行mock

    // method.go
    
    type User struct {
    	Name string
    	Birthday string
    }
    
    // CalcAge 计算用户年龄
    func (u *User) CalcAge() int {
    	t, err := time.Parse("2006-01-02", u.Birthday)
    	if err != nil {
    		return -1
    	}
    	return int(time.Now().Sub(t).Hours()/24.0)/365
    }
    
    
    // GetInfo 获取用户相关信息
    func (u *User) GetInfo()string{
    	age := u.CalcAge()
    	if age <= 0 {
    		return fmt.Sprintf("%s很神秘,我们还不了解ta。", u.Name)
    	}
    	return fmt.Sprintf("%s今年%d岁了,ta是我们的朋友。", u.Name, age)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    如果我们为GetInfo编写单元测试的时候CalcAge方法的功能还未完成,这个时候我们可以使用monkey进行打桩。

    // method_test.go
    
    func TestUser_GetInfo(t *testing.T) {
    	var u = &User{
    		Name:     "q1mi",
    		Birthday: "1990-12-20",
    	}
    
    	// 为对象方法打桩
    	monkey.PatchInstanceMethod(reflect.TypeOf(u), "CalcAge", func(*User)int {
    		return 18
    	})
    
    	ret := u.GetInfo()  // 内部调用u.CalcAge方法时会返回18
    	if !strings.Contains(ret, "朋友"){
    		t.Fatal()
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    执行单元测试:

    go test -run=User -v
    === RUN   TestUser_GetInfo
    --- PASS: TestUser_GetInfo (0.00s)
    PASS
    ok      monkey_demo     0.012s
    
    • 1
    • 2
    • 3
    • 4
    • 5

    monkey基本上能满足我们在单元测试中打桩的任何需求。

    社区中还有一个参考monkey库实现的gomonkey库,原理和使用过程基本相似,这里就不再啰嗦了。

    熟练使用各种打桩工具能够让我们更快速地编写合格的单元测试,为我们的软件保驾护航。

    本文通过外部函数依赖及内部方法依赖两个示例,介绍了如何使用monkey对依赖的函数和方法进行打桩。

    在下一篇中,我们将介绍编写单元测试时常用的工具——goconvey。

  • 相关阅读:
    Flink内部数据交换源码分析(一)
    Vue 组件的单元测试
    使用接口根据关键词取亚马逊商品数据
    ntohl()、htonl()、ntohs()、htons()函数
    将 ONLYOFFICE 文档编辑器与 С# 群件平台集成
    【超分】A Benchmark for Chinese-English Scene Text Image Super-resolution
    嵌入式分享合集34
    maven多模块依赖包程序包xxx不存在
    卷积核、特征图可视化
    3DEXPERIENCE® SOLIDWORKS® 新功能
  • 原文地址:https://blog.csdn.net/YouMing_Li/article/details/136633338