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写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编 程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美 剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰 富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术, 实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号