English | 简体中文
xgo 为 golang 提供全in一的测试工具,包括:
对于猴子补丁部分,xgo 作为 go run、go build 和 go test 的预处理器工作(参见我们的博客)。
顺便说一句,我保证这是一个有趣的项目。
go install github.com/xhd2015/xgo/cmd/xgo@latest
验证安装:
xgo version # 输出: # 1.0.x xgo help # 输出:帮助信息
如果找不到 xgo,你可能需要检查 $GOPATH/bin 是否已添加到你的 PATH 变量中。
对于 CI 作业(如 github 工作流),请参阅 doc/INSTALLATION.md。
xgo 需要至少 go1.17 才能编译。
对操作系统和架构没有特定限制。
所有操作系统和架构只要被 go 支持,就都被 xgo 支持。
| x86 | x86_64 (amd64) | arm64 | 任何其他架构... | |
|---|---|---|---|---|
| Linux | Y | Y | Y | Y |
| Windows | Y | Y | Y | Y |
| macOS | Y | Y | Y | Y |
| 任何其他操作系统... | Y | Y | Y | Y |
让我们用 xgo 编写一个单元测试:
xgo,并通过以下命令验证安装:xgo version # 输出 # 1.0.x
mkdir demo cd demo go mod init demo
demo_test.go:package demo_test import ( "testing" "github.com/xhd2015/xgo/runtime/mock" ) func MyFunc() string { return "my func" } func TestFuncMock(t *testing.T) { mock.Patch(MyFunc, func() string { return "mock func" }) text := MyFunc() if text != "mock func" { t.Fatalf("expect MyFunc() to be 'mock func', actual: %s", text) } }
github.com/xhd2015/xgo/runtime 依赖:go get github.com/xhd2015/xgo/runtime
xgo test -v ./ # 注意:xgo 首次设置时会花一些时间
输出:
=== RUN TestFuncMock --- PASS: TestFuncMock (0.00s) PASS ok demo
注意:使用 xgo 而不是 go 来测试你的代码。
在底层,xgo 预处理你的代码以添加模拟钩子,然后调用 go 完成剩余工作。
上述代码可以在 doc/demo 中找到。
在当前 goroutine 中修补给定函数。
API:
Patch(fn,replacer) func()速查表:
// 包级函数 mock.Patch(SomeFunc, mockFunc) // 每个实例的方法 // 只有绑定的实例 `v` 会被模拟 // `v` 可以是结构体或接口 mock.Patch(v.Method, mockMethod) // 每个 TParam 的泛型函数 // 只有指定的 `int` 版本会被模拟 mock.Patch(GenericFunc[int], mockFuncInt) // 每个 TParam 和实例的泛型方法 v := GenericStruct[int] mock.Mock(v.Method, mockMethod) // 闭包也可以被模拟 // 使用较少,但也支持 mock.Mock(closure, mockFunc)
参数:
fn 是一个简单函数(即包级函数、类型拥有的函数或闭包(是的,我们确实支持模拟闭包)),那么对该函数的所有调用都将被拦截,replacer 另一个将替换 fn 的函数范围:
Patch 在 init 中调用,那么所有 goroutine 都将被模拟。Patch 在 init 之后调用,那么模拟拦截器只对当前 goroutine 有效,其他 goroutine 不受影响。注意:fn 和 replacer 应该有相同的签名。
返回:
func() 可以用来在当前 goroutine 退出之前提前移除替换器Patch 在当前 goroutine 中用 replacer 替换给定的 fn。一旦当前 goroutine 退出,它将移除替换器。
示例:
package patch_test import ( "testing" "github.com/xhd2015/xgo/runtime/mock" ) func greet(s string) string { return "hello " + s } func TestPatchFunc(t *testing.T) { mock.Patch(greet, func(s string) string { return "mock " + s }) res := greet("world") if res != "mock world" { t.Fatalf("expect patched result to be %q, actual: %q", "mock world", res) } }
注意:Patch 和 Mock(下文)支持顶级变量和常量,参见 runtime/mock/MOCK_VAR_CONST.md。
模拟标准库的注意事项:模拟标准库函数有不同的模式,参见 runtime/mock/stdlib.md。
runtime/mock 还提供了另一个名为 Mock 的 API,类似于 Patch。
它们之间的唯一区别在于第二个参数:Mock 接受一个拦截器。
Mock 可以在 Patch 无法使用的情况下使用,比如具有未导出类型的函数。
API 详情:runtime/mock/README.md
Mock API:
Mock(fn, interceptor)参数:
fn 与 Patch 部分描述的相同fn 是一个方法(即 file.Read),那么只有对该实例的调用会被拦截,其他实例不受影响拦截器签名:func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error
nil,则目标函数被模拟,mock.ErrCallOld,则目标函数会被再次调用,还有其他 2 个 API 可用于基于名称设置模拟,详情请查看 runtime/mock/README.md。
方法模拟示例:
type MyStruct struct { name string } func (c *MyStruct) Name() string { return c.name } func TestMethodMock(t *testing.T){ myStruct := &MyStruct{ name: "my struct", } otherStruct := &MyStruct{ name: "other struct", } mock.Mock(myStruct.Name, func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error { results.GetFieldIndex(0).Set("mock struct") return nil }) // myStruct 受影响 name := myStruct.Name() if name!="mock struct"{ t.Fatalf("expect myStruct.Name() to be 'mock struct', actual: %s", name) } // otherStruct 不受影响 otherName := otherStruct.Name() if otherName!="other struct"{ t.Fatalf("expect otherStruct.Name() to be 'other struct', actual: %s", otherName) } }
Trace 可能是 xgo 提供的最强大的工具,这个博客有更详细的示例:https://blog.xhd2015.xyz/posts/xgo-trace_a-powerful-visualization-tool-in-go
在调试深层调用栈时会很痛苦。
Trace 通过收集分层堆栈跟踪并将其存储到文件中供以后使用来解决这个问题。
不用说,有了 Trace,调试变得不那么常见:
package trace_test import ( "fmt" "testing" ) func TestTrace(t *testing.T) { A() B() C() } func A() { fmt.Printf("A\n") } func B() { fmt.Printf("B\n");C(); } func C() { fmt.Printf("C\n") }
用 xgo 运行:
# 运行测试 # 这将把跟踪写入 TestTrace.json # --strace 表示堆栈跟踪 xgo test --strace ./ # 查看跟踪 xgo tool trace TestTrace.json
输出:

来自 runtime/test/stack_trace/update_test.go 的另一个更复杂的示例:

真实世界的例子:
Trace 帮助你快速上手新项目。
默认情况下,Trace 会将跟踪写入当前工作目录下的临时目录。可以通过将 XGO_TRACE_OUTPUT 设置为不同的值来覆盖此行为:
XGO_TRACE_OUTPUT=stdout:跟踪将写入标准输出,用于调试目的,XGO_TRACE_OUTPUT=<dir>:跟踪将写入 <dir>,XGO_TRACE_OUTPUT=off:关闭跟踪。除了 --strace 标志外,xgo 还允许你定义应该收集哪个跨度,使用 trace.Begin():
import "github.com/xhd2015/xgo/runtime/trace" func TestTrace(t *testing.T) { A() finish := trace.Begin() defer finish() B() C() }
跟踪将只包括 B() 和 C()。
Xgo 在调用 go 之前预处理源代码和 IR(中间表示),为用户提供在调用任何函数时拦截的机会。
Trap 允许开发人员动态拦截函数执行。 以下示例通过添加Trap拦截器来记录函数执行跟踪:
(查看 test/testdata/trap/trap.go 获取更多详情。)
package main import ( "context" "fmt" "github.com/xhd2015/xgo/runtime/core" "github.com/xhd2015/xgo/runtime/trap" ) func init() { trap.AddInterceptor(&trap.Interceptor{ Pre: func(ctx context.Context, f *core.FuncInfo, args core.Object, results core.Object) (interface{}, error) { if f.Name == "A" { fmt.Printf("trap A\n") return nil, nil } if f.Name == "B" { fmt.Printf("abort B\n") return nil, trap.ErrAbort } return nil, nil }, }) } func main() { A() B() } func A() { fmt.Printf("A\n") } func B() { fmt.Printf("B\n") }
使用 go 运行:
go run ./ # 输出: # A # B
使用 xgo 运行:
xgo run ./ # 输出: # trap A # A # abort B
AddInterceptor() 将给定的拦截器添加到全局或局部,取决于它是在 init 中调用还是在 init 之后调用:
init 之前: 对所有goroutine全局生效,init 之后: 仅对当前goroutine生效,并在当前goroutine退出后清除。当 AddInterceptor() 在 init 之后调用时,它将返回一个dispose函数,用于在当前goroutine退出之前提前清除拦截器。
示例:
func main(){ clear := trap.AddInterceptor(...) defer clear() ... }
Trap还有一个名为 Direct(fn) 的辅助函数,可用于绕过任何trap和模拟拦截器,直接调用原始函数。
xgo e 子命令将在浏览器中打开一个测试资源管理器UI,提供一种简单的方法来测试和调试go代码。
使用测试资源管理器时,会使用 xgo test 而不是 go test,以启用模拟功能。
<img width="1792" alt="test-explorer" src="https://github.com/xhd2015/xgo/assets/14964938/f8689a2d-b422-4101-acef-0aa41ad7e246">$ xgo e 服务器监听于 http://localhost:7070
注:xgo e 是 xgo tool test-explorer 的别名。
如需帮助,运行 xgo e help。
有关配置详情,请参阅 doc/test-explorer/README.md。
xgo tool coverage 子命令扩展了go内置的 go tool cover,以实现更好的可视化。
首先,运行 go test 或 xgo test 获取覆盖率配置文件:
go test -cover -coverpkg ./... -coverprofile cover.out ./...
然后,使用 xgo 显示覆盖率:
xgo tool coverage serve cover.out
输出:

显示的覆盖率是覆盖率和git差异的组合。默认情况下,只显示修改的行:
这有助于快速定位未覆盖的更改,并逐步为它们添加测试。
我知道你们使用其他猴子补丁库的人因这些框架隐含的不安全性而困扰。
但我向你保证,xgo中的模拟是内置的并发安全的。这意味着,你可以随心所欲地并发运行多个测试。
为什么?当你运行一个测试时,你设置了一些模拟,这些模拟只会影响运行测试的goroutine。当goroutine结束时,无论测试通过还是失败,这些模拟都会被清除。
想知道为什么吗?敬请期待,我们正在编写内部文档。
正在进行中...
有关更多详情,请参阅 Issue#7。
这篇博客有一个基本解释: https://blog.xhd2015.xyz/posts/xgo-monkey-patching-in-go-using-toolexec
xgo?原因很简单:没有接口。
是的,没有接口,仅用于模拟。如果抽象接口的唯一原因是为了模拟,那只会让我感到无聊,而不是工作。
仅为模拟而提取接口对我来说从不是一个选项。对于问题的领域来说,它仅仅是一个变通方法。它强制代码以一种风格编写,这就是我们不喜欢它的原因。
猴子补丁简单地为问题做了正确的事。但现有的库在兼容性方面表现不佳。
所以我创建了 xgo,希望它最终能取代其他解决模拟问题的方案。
xgo 与 monkey 进行比较项目 bouk/monkey 最初由bouk创建,如他的博客 https://bou.ke/blog/monkey-patching-in-go 所述。
简而言之,它使用低级汇编黑客技术在运行时替换函数。随着它被越来越广泛地使用(尤其是在macOS上),这给用户带来了许多令人困惑的问题。
后来,作者本人将其存档并不再维护。然而,后来有两个项目接管了ASM的想法,并为较新的go版本和架构(如Apple M1)添加了支持。
尽管如此,这两个项目并没有解决ASM引入的底层兼容性问题,包括跨平台支持、需要写入执行代码的只读部分以及缺乏通用模拟。
因此,开发人 员仍然时不时会遇到令人烦恼的故障。
Xgo通过避免对语言本身进行低级黑客攻击来解决这些问题。相反,它依赖于go编译器使用的IR表示。
它在编译器编译源代码时对IR(中间表示)进行所谓的"IR重写"。IR比机器代码更接近源代码。因此,它比monkey解决方案要稳定得多。
总之,xgo 和monkey的比较如下:
| xgo | monkey | |
|---|---|---|
| 技术 | IR | ASM |
| 函数模拟 | 是 | 是 |
| 未导出函数模拟 | 是 | 否 |
| 每实例方法模拟 | 是 | 否 |
| 每Goroutine模拟 | 是 | 否 |
| 每泛型类型模拟 | 是 | 是 |
| 变量模拟 | 是 | 否 |
| 常量模拟 | 是 | 否 |
| 闭包模拟 | 是 | 是 |
| 堆栈跟踪 | 是 | 否 |
| 通用陷阱 | 是 | 否 |
| 兼容性 | 无限制 | 限于amd64和arm64 |
| API | 简单 | 复杂 |
| 集成工作量 | 容易 | 困难 |
想要帮助贡献 xgo 吗?太好了!查看 CONTRIBUTING
获取帮助。
xgo 的演变xgo 是原始 go-mock 的继任者,后者通过在编译前重写go代码来工作。
go-mock 采用的策略运行良好,但由于源代码膨胀,导致大型项目的构建时间大大延长。
然而,go-mock 在发现Trap、Trace和Mock之外的功能方面非常出色,还具有额外的能力,如捕获变量和禁用map随机性。
它是 xgo 所站立的肩膀。


AI赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。


企业专属的AI法律顾问
iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。


稳定高效的流量提升解决方案,助力品牌曝光
稳定高效的流量提升解决方案,助力品牌曝光


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频


实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。


选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。


AI辅助编程,代码自动修复
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


最强AI数据分析助手
小浣熊家族Raccoon,您的AI智能助手,致力于通过先进的人工智能技术,为用户提供高效、便捷的智能服务。无论是日常咨询还是专业问题解答,小浣熊都能以快速、准确的响应满足您的需求,让您的生活更加智能便捷。


像人一样思考的AI智能体
imini 是一款超级AI智能体,能根据人类指令,自主思考、自主完成、并且交付结果的AI智能体。


AI数字人视频创作平台
Keevx 一款开箱即用的AI数字人视频创作平台,广泛适用于电商广告、企业培训与社媒宣传,让全球 企业与个人创作者无需拍摄剪辑,就能快速生成多语言、高质量的专业视频。
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号