gomonkey
gomonkey是一款强大的运行时打桩Mock工具/动态 Mock 工具能够在不修改源代码的前提下对函数、方法、全局变量等进行动态替换广泛用于单元测试场景。工具很全面可以针对数据库外部请求http接口变量和结构体等打桩不过个人认为对于http接口和数据库还是使用上面的两个方法要方便一些优点无侵入式打桩无需修改业务代码功能全面支持函数、方法、全局变量等多种打桩场景支持私有成员打桩适配遗留项目轻量级易用API 简洁兼容主流框架灵活控制打桩生命周期精准适配测试需求x86_64 (Intel/AMD)架构下功能基本完整、稳定是生产级 Mock 工具致命缺陷对Windows、Mac、arm架构系统支持很不友好在 ARM64 (aarch64) 架构下存在 大量核心功能失效、运行时崩溃、Mock 无效果 的缺陷缺陷是 gomonkey 的底层实现原理 导致的而非 Go 语言 / ARM64 的兼容性问题官方至今未彻底修复Go1.18 版本中更严重Go1.17 及以下 ARM64 版本问题稍少但依然存在关键缺陷1.安装github地址go get github.com/agiledragon/gomonkey/v22.方法简介官方推荐命令禁用内联优化go test -gcflagsall-lgomonkey有两种调用形式全局函数调用结构体方法调用区别归属关系完全不同前者包级别全局函数不属于任何结构体直接通过包名调用即可后者结构体的指针接收者成员方法属于结构体的一部分必须通过Patches结构体的实例对象指针才能调用。调用前置条件不同前者调用前不需要手动创建Patches实例全局函数内部帮你自动创建直接调用即可后者调用前必须先手动创建一个Patches实例底层执行逻辑 - 最核心的调用链路不同全局函数的执行链路一行顶三步步骤1内部调用 create() 创建一个全新的空 Patches 实例步骤2立刻调用这个新实例的 成员方法 ApplyMethodFunc步骤3返回这个实例对象本身链式调用基础补充源码中的create()等价于NewPatches()都是初始化空的 Patches 结构体结构体成员方法的执行链路根据入参target结构体 / 结构体指针 /reflect.Type和methodName反射获取目标方法通过funcToMethod做「普通函数 → 结构体方法」的转换核心逻辑后面讲调用ApplyCore执行底层的内存指令改写打桩核心函数地址跳转返回Patches实例本身支持链式调用。补充是真实的核心实现做了所有的实际工作共同点最终实现的业务功能完全一致都是为「结构体的指定方法」打桩替换原方法的执行逻辑入参校验、底层打桩逻辑完全一致全局函数只是转发调用所有的校验方法是否存在、函数签名是否匹配、内存指令改写都是结构体成员方法做的都支持链式调用返回值都是*Patches都可以继续追加.ApplyXXX()系列方法都需要手动调用Reset()还原桩不管是全局函数返回的实例还是手动创建的实例最终都要调用Reset()否则会导致后续测试被污染。方法目录官方包源码文件地址package gomonkeyimport (fmtreflectsyscallunsafegithub.com/agiledragon/gomonkey/v2/creflect)type Patches struct {originals map[uintptr][]bytetargets map[uintptr]uintptrvalues map[reflect.Value]reflect.ValuevalueHolders map[reflect.Value]reflect.Value}type Params []interface{}type OutputCell struct {Values ParamsTimes int}func ApplyFunc(target, double interface{}) *Patches {return create().ApplyFunc(target, double)}func ApplyMethod(target interface{}, methodName string, double interface{}) *Patches {return create().ApplyMethod(target, methodName, double)}func ApplyMethodFunc(target interface{}, methodName string, doubleFunc interface{}) *Patches {return create().ApplyMethodFunc(target, methodName, doubleFunc)}func ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches {return create().ApplyPrivateMethod(target, methodName, double)}func ApplyGlobalVar(target, double interface{}) *Patches {return create().ApplyGlobalVar(target, double)}func ApplyFuncVar(target, double interface{}) *Patches {return create().ApplyFuncVar(target, double)}func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {return create().ApplyFuncSeq(target, outputs)}func ApplyMethodSeq(target interface{}, methodName string, outputs []OutputCell) *Patches {return create().ApplyMethodSeq(target, methodName, outputs)}func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {return create().ApplyFuncVarSeq(target, outputs)}func ApplyFuncReturn(target interface{}, output ...interface{}) *Patches {return create().ApplyFuncReturn(target, output...)}func ApplyMethodReturn(target interface{}, methodName string, output ...interface{}) *Patches {return create().ApplyMethodReturn(target, methodName, output...)}func ApplyFuncVarReturn(target interface{}, output ...interface{}) *Patches {return create().ApplyFuncVarReturn(target, output...)}func create() *Patches {return Patches{originals: make(map[uintptr][]byte), targets: map[uintptr]uintptr{},values: make(map[reflect.Value]reflect.Value), valueHolders: make(map[reflect.Value]reflect.Value)}}func NewPatches() *Patches {return create()}func (this *Patches) Origin(fn func()) {for target, bytes : range this.originals {modifyBinary(target, bytes)}fn()for target, targetPtr : range this.targets {code : buildJmpDirective(targetPtr)modifyBinary(target, code)}}func (this *Patches) ApplyFunc(target, double interface{}) *Patches {t : reflect.ValueOf(target)d : reflect.ValueOf(double)return this.ApplyCore(t, d)}func (this *Patches) ApplyMethod(target interface{}, methodName string, double interface{}) *Patches {m, ok : castRType(target).MethodByName(methodName)if !ok {panic(retrieve method by name failed)}d : reflect.ValueOf(double)return this.ApplyCore(m.Func, d)}func (this *Patches) ApplyMethodFunc(target interface{}, methodName string, doubleFunc interface{}) *Patches {m, ok : castRType(target).MethodByName(methodName)if !ok {panic(retrieve method by name failed)}d : funcToMethod(m.Type, doubleFunc)return this.ApplyCore(m.Func, d)}func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches {m, ok : creflect.MethodByName(castRType(target), methodName)if !ok {panic(retrieve method by name failed)}d : reflect.ValueOf(double)return this.ApplyCoreOnlyForPrivateMethod(m, d)}func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches {t : reflect.ValueOf(target)if t.Type().Kind() ! reflect.Ptr {panic(target is not a pointer)}this.values[t] reflect.ValueOf(t.Elem().Interface())d : reflect.ValueOf(double)t.Elem().Set(d)return this}func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches {t : reflect.ValueOf(target)d : reflect.ValueOf(double)if t.Type().Kind() ! reflect.Ptr {panic(target is not a pointer)}this.check(t.Elem(), d)return this.ApplyGlobalVar(target, double)}func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {funcType : reflect.TypeOf(target)t : reflect.ValueOf(target)d : getDoubleFunc(funcType, outputs)return this.ApplyCore(t, d)}func (this *Patches) ApplyMethodSeq(target interface{}, methodName string, outputs []OutputCell) *Patches {m, ok : castRType(target).MethodByName(methodName)if !ok {panic(retrieve method by name failed)}d : getDoubleFunc(m.Type, outputs)return this.ApplyCore(m.Func, d)}func (this *Patches) ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {t : reflect.ValueOf(target)if t.Type().Kind() ! reflect.Ptr {panic(target is not a pointer)}if t.Elem().Kind() ! reflect.Func {panic(target is not a func)}funcType : reflect.TypeOf(target).Elem()double : getDoubleFunc(funcType, outputs).Interface()return this.ApplyGlobalVar(target, double)}func (this *Patches) ApplyFuncReturn(target interface{}, returns ...interface{}) *Patches {funcType : reflect.TypeOf(target)t : reflect.ValueOf(target)outputs : []OutputCell{{Values: returns, Times: -1}}d : getDoubleFunc(funcType, outputs)return this.ApplyCore(t, d)}func (this *Patches) ApplyMethodReturn(target interface{}, methodName string, returns ...interface{}) *Patches {m, ok : reflect.TypeOf(target).MethodByName(methodName)if !ok {panic(retrieve method by name failed)}outputs : []OutputCell{{Values: returns, Times: -1}}d : getDoubleFunc(m.Type, outputs)return this.ApplyCore(m.Func, d)}func (this *Patches) ApplyFuncVarReturn(target interface{}, returns ...interface{}) *Patches {t : reflect.ValueOf(target)if t.Type().Kind() ! reflect.Ptr {panic(target is not a pointer)}if t.Elem().Kind() ! reflect.Func {panic(target is not a func)}funcType : reflect.TypeOf(target).Elem()outputs : []OutputCell{{Values: returns, Times: -1}}double : getDoubleFunc(funcType, outputs).Interface()return this.ApplyGlobalVar(target, double)}func (this *Patches) Reset() {for target, bytes : range this.originals {modifyBinary(target, bytes)delete(this.originals, target)}for target, variable : range this.values {target.Elem().Set(variable)}}func (this *Patches) ApplyCore(target, double reflect.Value) *Patches {this.check(target, double)assTarget : *(*uintptr)(getPointer(target))original : replace(assTarget, uintptr(getPointer(double)))if _, ok : this.originals[assTarget]; !ok {this.originals[assTarget] original}this.targets[assTarget] uintptr(getPointer(double))this.valueHolders[double] doublereturn this}func (this *Patches) ApplyCoreOnlyForPrivateMethod(target unsafe.Pointer, double reflect.Value) *Patches {if double.Kind() ! reflect.Func {panic(double is not a func)}assTarget : *(*uintptr)(target)original : replace(assTarget, uintptr(getPointer(double)))if _, ok : this.originals[assTarget]; !ok {this.originals[assTarget] original}this.targets[assTarget] uintptr(getPointer(double))this.valueHolders[double] doublereturn this}func (this *Patches) check(target, double reflect.Value) {if target.Kind() ! reflect.Func {panic(target is not a func)}if double.Kind() ! reflect.Func {panic(double is not a func)}targetType : target.Type()doubleType : double.Type()if targetType.NumIn() doubleType.NumIn() ||targetType.NumOut() ! doubleType.NumOut() ||(targetType.NumIn() doubleType.NumIn() targetType.IsVariadic() ! doubleType.IsVariadic()) {panic(fmt.Sprintf(target type(%s) and double type(%s) are different, target.Type(), double.Type()))}for i, size : 0, doubleType.NumIn(); i size; i {targetIn : targetType.In(i)doubleIn : doubleType.In(i)if targetIn.AssignableTo(doubleIn) {continue}panic(fmt.Sprintf(target type(%s) and double type(%s) are different, target.Type(), double.Type()))}for i, size : 0, doubleType.NumOut(); i size; i {targetOut : targetType.Out(i)doubleOut : doubleType.Out(i)if targetOut.AssignableTo(doubleOut) {continue}panic(fmt.Sprintf(target type(%s) and double type(%s) are different, target.Type(), double.Type()))}}func replace(target, double uintptr) []byte {code : buildJmpDirective(double)bytes : entryAddress(target, len(code))original : make([]byte, len(bytes))copy(original, bytes)modifyBinary(target, code)return original}func getDoubleFunc(funcType reflect.Type, outputs []OutputCell) reflect.Value {if funcType.NumOut() ! len(outputs[0].Values) {panic(fmt.Sprintf(func type has %v return values, but only %v values provided as double,funcType.NumOut(), len(outputs[0].Values)))}needReturn : falseslice : make([]Params, 0)for _, output : range outputs {if output.Times -1 {needReturn trueslice []Params{output.Values}break}t : 0if output.Times 1 {t 1} else {t output.Times}for j : 0; j t; j {slice append(slice, output.Values)}}i : 0lenOutputs : len(slice)return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {if needReturn {return GetResultValues(funcType, slice[0]...)}if i lenOutputs {ireturn GetResultValues(funcType, slice[i-1]...)}panic(double seq is less than call seq)})}func GetResultValues(funcType reflect.Type, results ...interface{}) []reflect.Value {var resultValues []reflect.Valuefor i, r : range results {var resultValue reflect.Valueif r nil {resultValue reflect.Zero(funcType.Out(i))} else {v : reflect.New(funcType.Out(i))v.Elem().Set(reflect.ValueOf(r))resultValue v.Elem()}resultValues append(resultValues, resultValue)}return resultValues}type funcValue struct {_ uintptrp unsafe.Pointer}func getPointer(v reflect.Value) unsafe.Pointer {return (*funcValue)(unsafe.Pointer(v)).p}func entryAddress(p uintptr, l int) []byte {return *(*[]byte)(unsafe.Pointer(reflect.SliceHeader{Data: p, Len: l, Cap: l}))}func pageStart(ptr uintptr) uintptr {return ptr ^(uintptr(syscall.Getpagesize() - 1))}func funcToMethod(funcType reflect.Type, doubleFunc interface{}) reflect.Value {rf : reflect.TypeOf(doubleFunc)if rf.Kind() ! reflect.Func {panic(doubleFunc is not a func)}vf : reflect.ValueOf(doubleFunc)return reflect.MakeFunc(funcType, func(in []reflect.Value) []reflect.Value {if funcType.IsVariadic() {return vf.CallSlice(in[1:])} else {return vf.Call(in[1:])}})}func castRType(val interface{}) reflect.Type {if rTypeVal, ok : val.(reflect.Type); ok {return rTypeVal}return reflect.TypeOf(val)}