{"id":19965550,"url":"https://github.com/tencent/goom","last_synced_at":"2025-05-03T23:30:49.519Z","repository":{"id":213264057,"uuid":"721911152","full_name":"Tencent/goom","owner":"Tencent","description":"go语言mock库， 用于编写go语言单元测试时，mock函数、接口等场景","archived":false,"fork":false,"pushed_at":"2025-02-10T13:12:44.000Z","size":844,"stargazers_count":48,"open_issues_count":1,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-07T23:11:09.815Z","etag":null,"topics":["devops","go","golang","mock","tdd","test","test-automation","testing","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tencent.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"License.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-22T03:11:07.000Z","updated_at":"2025-03-14T06:14:53.000Z","dependencies_parsed_at":"2024-11-13T02:31:47.892Z","dependency_job_id":"38082e2e-b242-4b86-8f54-6629bf7092fb","html_url":"https://github.com/Tencent/goom","commit_stats":null,"previous_names":["tencent/goom"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tencent%2Fgoom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tencent%2Fgoom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tencent%2Fgoom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tencent%2Fgoom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tencent","download_url":"https://codeload.github.com/Tencent/goom/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252269026,"owners_count":21721239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","go","golang","mock","tdd","test","test-automation","testing","unit-testing"],"created_at":"2024-11-13T02:29:28.232Z","updated_at":"2025-05-03T23:30:49.513Z","avatar_url":"https://github.com/Tencent.png","language":"Go","readme":"# GOOM单测Mock框架\n## 动态\n- 开源版本将持续进行维护，欢迎提交issue\n- 或者加入QQ群沟通\n\n## 介绍\n### 背景\n1. 基于腾讯公司内部需要一款适合公司业务迭代速度和稳定性要求的mock库，国内众多项目采用外界开源的gomonkey框架进行函数的mock，因其版本更新不及时, 加上团队目前实现一款改进版-支持异包未导出函数mock，同包未导出方法mock，无需unpath即可在mock过程中调用原函数的特性，可以支持到延迟模拟，参数更改，mock数据录制等功能，因此建立此项目\n2. 目前有一半以上方案是基于gomock类似的实现方案, 此mock方案需要要求业务代码具备良好的接口设计，才能顺利生成mock代码，而goom只需要指定函数名称或函数定义，就能支持到任意函数的mock，任意函数异常注入，延时模拟等扩展性功能\n3. 当前实现已经支持Mac、Mac(ARM)、linux、windows等多种平台，支持go1.16-go1.23等各个版本, 解决permission_denied等问题，且对未来版本友好支持\n\n### 功能特性\n1. mock过程中调用原函数(线程安全, 支持并发单测)\n2. 异常注入，对函数调用支持异常注入，延迟模拟等稳定性测试\n3. 所有操作都是并发安全的\n4. 未导出(未导出)函数(或方法)的mock(不建议使用, 对于未导出函数的Mock 通常都是因为代码设计可能有问题, 此功能会在未来版本中废弃)\n5. 支持M1 mac环境运行，支持IDE debug，函数、方法mock，接口mock，未导出函数mock，等能力均可在arm64架构上使用\n\n### 将来\n1. 支持数据驱动测试\n2. 支持Mock锚点定义\n3. 支持代码重构\n\n## 注意！！！不要过度依赖mock\n\n\u003e [1.千万不要过度依赖于mock](https://mp.weixin.qq.com/s?__biz=MzA5MTAzNjU1OQ==\u0026mid=2454780683\u0026idx=1\u0026sn=aabc85f3bd2cfa21b8b806bad581f0c5)\n\u003e\n\u003e 2.对于正规的第三方库，比如mysql、gorm的库本身会提供mock能力, 可参考[sql_test.go](https://github.com/Tencent/goom/wiki/sql-mock%E6%A1%88%E4%BE%8B)\n\u003e\n\u003e 3.对于自建的内部依赖库, 建议由库的提供方编写mock(1.使用方无需关心提供方的实现细节、2.由库提供方负责版本升级时mock实现逻辑的更新)\n\n## Install\n```bash\n# 支持的golang版本: go1.11-go1.18\ngo get github.com/tencent/goom\n```\n\n## Tips\n```\n注意: 按照go编译规则，短函数会被内联优化，导致无法mock的情况，编译参数需要加上 -gcflags=all=-l 关闭内联\n例如: go test -gcflags=all=-l hello.go\n```\n\n## Getting Start\n```golang\n// 在需要使用mock的测试文件import\nimport \"github.com/tencent/goom\"\n```\n### 1. 基本使用\n#### 1.1. 函数mock\n```golang\n// foo 函数定义如下\nfunc foo(i int) int {\n    //...\n    return 0\n}\n\n// mock示例\n// 创建当前包的mocker\nmock := mocker.Create()\n\n// mock函数foo并设定返回值为1\nmock.Func(foo).Return(1)\ns.Equal(1, foo(0), \"return result check\")\n\n// 可搭配When使用: 参数匹配时返回指定值\nmock.Func(foo).When(1).Return(2)\ns.Equal(2, foo(1), \"when result check\")\n\n// 使用arg.In表达式,当参数为1、2时返回值为100\nmock.Func(foo).When(arg.In(1, 2)).Return(100)\ns.Equal(100, foo(1), \"when in result check\")\ns.Equal(100, foo(2), \"when in result check\")\n\n// 按顺序依次返回(等价于gomonkey的Sequence)\nmock.Func(foo).Returns(1, 2, 3)\ns.Equal(1, foo(0), \"returns result check\")\ns.Equal(2, foo(0), \"returns result check\")\ns.Equal(3, foo(0), \"returns result check\")\n\n// mock函数foo，使用Apply方法设置回调函数\n// 注意: Apply和直接使用Return都可以实现mock，两种方式二选一即可\n// Apply可以在桩函数内部实现自己的逻辑，比如根据不同参数返回不同值等等。\nmock.Func(foo).Apply(func(int) int {\n    return 1\n})\ns.Equal(1, foo(0), \"apply callback check\")\n\n\n// bar 多参数函数\nfunc bar(i interface{}, j int) int {\n    //...\n    return 0\n}\n\n// 忽略第一个参数, 当第二个参数为1、2时返回值为100\nmock.Func(bar).When(arg.Any(), arg.In(1, 2)).Return(100)\ns.Equal(100, bar(-1, 1), \"any param result check\")\ns.Equal(100, bar(0, 1), \"any param result check\")\ns.Equal(100, bar(1, 2), \"any param result check\")\ns.Equal(100, bar(999, 2), \"any param result check\")\n```\n\n#### 1.2. 结构体方法mock\n```golang\n// 结构体定义如下\ntype Struct1 struct{\n}\n\n// Call 导出方法\nfunc (f *Struct1) Call(i int) int {\n    return i\n}\n\n// mock示例\n// 创建当前包的mocker\nmock := mocker.Create()\n\n// mock 结构体Struct1的方法Call并设置其回调函数\n// 注意: 当使用Apply方法时，如果被mock对象为结构体方法, 那么Apply参数func()的第一个参数必须为接收体(即结构体/指针类型)\n// 其中, func (f *Struct1) Call(i int) int 和 \u0026Struct1{} 与 _ *Struct1同时都是带指针的接受体类型, 需要保持一致\nmock.Struct(\u0026Struct1{}).Method(\"Call\").Apply(func(_ *Struct1, i int) int {\n    return i * 2\n })\n\n// mock 结构体struct1的方法Call并返回1\n// 简易写法直接Return方法的返回值, 无需关心方法签名\nmock.Struct(\u0026Struct1{}).Method(\"Call\").Return(1)\n```\n\n#### 1.3. 结构体的未导出方法mock\n```golang\n\n// call 未导出方法示例\nfunc (f *Struct1) call(i int) int {\n    return i\n}\n\n// mock 结构体Struct1的未导出方法call, mock前先调用ExportMethod将其导出，并设置其回调函数\nmock.Struct(\u0026Struct1{}).ExportMethod(\"call\").Apply(func(_ *Struct1, i int) int {\n    return i * 2\n})\n\n// mock 结构体Struct1的未导出方法call, mock前先调用ExportMethod将其导出为函数类型，后续支持设置When, Return等\n// As调用之后，请使用Return或When API的方式来指定mock返回。\nmock.Struct(\u0026Struct1{}).ExportMethod(\"call\").As(func(_ *Struct1, i int) int {\n    // 随机返回值即可; 因后面已经使用了Return,此函数不会真正被调用, 主要用于指定未导出函数的参数签名\n    return i * 2\n}).Return(1)\n```\n\n### 2. 接口Mock\n接口定义举例:\n```golang\n// I 接口测试\ntype I interface {\n  Call(int) int\n  Call1(string) string\n  call2(int32) int32\n}\n```\n\n被测接口实例代码:\n```golang\n// TestTarget 被测对象\ntype TestTarget struct {\n\t// field 被测属性(接口类型)\n\tfield I\n}\n\nfunc NewTestTarget(i I) *TestTarget {\n\treturn \u0026TestTarget{\n\t\tfield:i,\n\t}\n}\n\nfunc (t *TestTarget) Call(num int) int {\n\treturn field.Call(num)\n}\n\nfunc (t *TestTarget) Call1(str string) string {\n    return  field.Call1(str)\n}\n```\n\n接口属性/变量Mock示例:\n```golang\nmock := mocker.Create()\n\n// 初始化接口变量\ni := (I)(nil)\n\n// 将Mock应用到接口变量\n// 1. interface mock只对mock.Interface(\u0026目标接口变量) 的目标接口变量生效, 因此需要将被测逻辑结构中的I类型属性或变量替换为i,mock才可生效\n// 2. 一般建议使用struct mock即可。\n// 3. Apply调用的第一个参数必须为*mocker.IContext, 作用是指定接口实现的接收体; 后续的参数原样照抄。\nmock.Interface(\u0026i).Method(\"Call\").Apply(func(ctx *mocker.IContext, i int) int {\n    return 100\n})\n\n// ===============================================================================\n// !!! 如果是mock interface的话，需要将interface i变量赋值替换【被测对象】的【属性】,才能生效\n// 也就是说,不对该接口的所有实现类实例生效。\nt := NewTestTarget(i)\n\n// 断言mock生效\ns.Equal(100, t.Call(1), \"interface mock check\")\n\nmock.Interface(\u0026i).Method(\"Call1\").As(func(ctx *mocker.IContext, i string) string {\n    // 随机返回值即可; 因后面已经使用了Return,此函数不会真正被调用, 主要用于指定未导出函数的参数签名\n\treturn \"\"\n}).When(\"\").Return(\"ok\")\ns.Equal(\"ok\", t.Call1(\"\"), \"interface mock check\")\n\n// Mock重置, 接口变量将恢复原来的值\nmock.Reset()\ns.Equal(nil, i, \"interface mock reset check\")\n```\n\n### 3. 高阶用法\n#### 3.1. 外部package的未导出函数mock(一般不建议对不同包下的未导出函数进行mock)\n```golang\n// 针对其它包的mock示例\n// 创建指定包的mocker，设置引用路径\nmock := mocker.Create()\n\n// mock函数foo1并设置其回调函数\nmock.Pkg(\"github.com/tencent/goom_test\").ExportFunc(\"foo1\").Apply(func(i int) int {\n    return i * 3\n})\n\n// mock函数foo1并设置其返回值\nmock.ExportFunc(\"foo1\").As(func(i int) int {\n    // 随机返回值即可; 因后面已经使用了Return,此函数不会真正被调用, 主要用于指定未导出函数的参数签名\n    return 0\n}).Return(1)\n```\n\n#### 3.2. 外部package的未导出结构体的mock(一般不建议对不同包下的未导出结构体进行mock)\n```golang\n// 针对其它包的mock示例\npackage https://github.com/tencent/goom/a\n\n// struct2 要mock的目标结构体\ntype struct2 struct {\n    field1 \u003ctype\u003e\n    // ...\n}\n\n```\n\nMock代码示例:\n```golang\npackage https://github.com/tencent/goom/b\n\n// fake fake一个结构体, 用于作为回调函数的Receiver\ntype fake struct {\n    // fake结构体要和原未导出结构体的内存结构对齐\n    // 即: 字段个数、顺序、类型必须一致; 比如: field1 \u003ctype\u003e 如果有\n    // 此结构体无需定义任何方法\n\tfield1 \u003ctype\u003e\n    // ...\n}\n\n// 创建指定包的mocker，设置引用路径\nmock := mocker.Create()\n\n// mock其它包的未导出结构体struct2的未导出方法call，并设置其回调函数\n// 如果参数是未导出的，那么需要在当前包fake一个同等结构的struct(只需要fake结构体，方法不需要fake)，fake结构体要和原未导出结构体struct2的内存结构对齐\n// 注意: 如果方法是指针方法，那么需要给struct加上*，比如:ExportStruct(\"*struct2\")\nmock.Pkg(\"https://github.com/tencent/goom/a\").ExportStruct(\"struct2\").Method(\"call\").Apply(func(_ *fake, i int) int {\n    return 1\n})\ns.Equal(1, struct2Wrapper.call(0), \"unexported struct mock check\")\n\n// mock其它包的未导出结构体struct2的未导出方法call，并设置其返回值\nmock.ExportStruct(\"struct2\").Method(\"call\").As(func(_ *fake, i int) int {\n\t// 随机返回值即可; 因后面已经使用了Return,此函数不会真正被调用, 主要用于指定接口方法的参数签名\n    return 0\n}).Return(1) // 指定返回值\ns.Equal(1, struct2Wrapper.call(0), \"unexported struct mock check\")\n```\n\n### 4. 追加多个返回值序列\n```golang\nmock := mocker.Create()\n\n// 设置函数foo当传入参数为1时，第一次返回3，第二次返回2\nwhen := mock.Func(foo).When(1).Return(0)\nfor i := 1;i \u003c= 100;i++ {\n    when.AndReturn(i)\n}\ns.Equal(0, foo(1), \"andReturn result check\")\ns.Equal(1, foo(1), \"andReturn result check\")\ns.Equal(2, foo(1), \"andReturn result check\")\n ...\n```\n\n### 5. 在回调函数中调用原函数\n```golang\nmock := mocker.Create()\n\n// 定义原函数,用于占位,实际不会执行该函数体\n// 需要和原函数的参数列表保持一致\n// 定义原函数,用于占位,实际不会执行该函数体\nvar origin = func(i int) int {\n    // 用于占位, 实际不会执行该函数体; 因底层trampoline技术的占位要求, 必须编写方法体\n    fmt.Println(\"only for placeholder, will not call\")\n    // return 指定随机返回值即可\n    return 0\n}\n\nmock.Func(foo1).Origin(\u0026origin).Apply(func(i int) int {\n    // 调用原函数\n    originResult := origin(i)\n\n    // 加入延时逻辑等\n    time.Sleep(time.Seconds)\n\n    return originResult + 100\n})\n// foo1(1) 等待1秒之后返回:101\ns.Equal(101, foo1(1), \"call origin result check\")\n```\n\n## 问题答疑\n常见问题:\n1. 如果是M1-MAC(arm CPU)机型, 可以尝试以下两种方案\n\na. 尝试使用权限修复工具,在项目根目录执行以下指令:\n```shell\nMOCKER_DIR=$(go list -m -f '{{.Dir}}' github.com/tencent/goom)\n${MOCKER_DIR}/tool/permission_denied.sh -i\n```\n\nb: 如果a方案没有效果，则尝试切换成amd的go编译器,在环境变量中添加:\n```shell\nGOARCH=amd64\n```\n\n2. 如果遇到mock未生效的问题,可以打开debug日志进行自助排查\n```go\n// TestUnitTestSuite 测试入口\nfunc TestUnitTestSuite(t *testing.T) {\n\t// 开启debug模式, 在控制台可以\n\t// 1.查看apply和reset的状态日志\n\t// 2.查看mock调用日志\n\tmocker.OpenDebug()\n\tsuite.Run(t, new(mockerTestSuite))\n}\n```\n3. windows系统下,请加上构建参数以打开符号表编译: -ldflags=\"-s=false\", 比如\n```shell\ngo test -ldflags=\"-s=false\" -gcflags \"all=-N -l\" ./...\n```\n\n\n4. go 1.23以上版本需要加上以下构建参数才可以使用    \n报错内容:\n```\nlink: github.com/tencent/goom/internal/hack: invalid reference to runtime.firstmoduledata\n```\n解决方案1，添加构建参数:\n```shell\n-gcflags=\"all=-N -l\" -ldflags=-checklinkname=0\n```\n\u003e -gcflags=\"all=-N -l\": 解决被mock函数内联问题，和permission denied的问题(go1.23版本新增要求)  \n\u003e -checklinkname=0: 关闭golinkname的标签检查，即继续允许go:linkname标签的使用   \n\n\n解决方案2: 升级到最新版本:\nv1.0.4-rc1\n此版本处于公测阶段，目前自测在windows、mac、mac(ARM)、linux，go1.16-go1.23版本均可使用\n未来更高的go版本，理论上也能较好支持\n\n\n## 联系答疑\n常见问题可参考: https://github.com/Tencent/goom/wiki\n\n或者提issue: https://github.com/Tencent/goom/issues\n\n\n## Contributor\n@yongfuchen、@adrewchen、@bingjgyan、@mingjiehu、@ivyyi、@miliao\n\n\n# QQ群\n\u003cimg src=\"https://github.com/user-attachments/assets/ae5fc289-f47c-45c8-b8c2-688cffe8eda0\" alt=\"扫码加QQ群\" width=\"350\" /\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftencent%2Fgoom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftencent%2Fgoom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftencent%2Fgoom/lists"}