golang实现代码故障注入与测试的failpoint插件使用
Golang实现代码故障注入与测试的failpoint插件使用
快速开始(使用failpoint-ctl)
- 从源码构建
failpoint-ctl
git clone https://github.com/pingcap/failpoint.git
cd failpoint
make
ls bin/failpoint-ctl
- 在你的程序中注入故障点,例如:
package main
import "github.com/pingcap/failpoint"
func main() {
failpoint.Inject("testPanic", func() {
panic("failpoint triggerd")
})
}
-
使用
failpoint-ctl enable
转换你的代码 -
使用
go build
构建 -
使用
GO_FAILPOINTS
环境变量启用故障点
GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
注意:GO_FAILPOINTS
不适用于InjectCall
类型的标记。
- 如果你使用
go run
运行测试,不要忘记在命令中添加生成的binding__failpoint_binding__.go
,例如:
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
完整示例Demo
package main
import (
"fmt"
"github.com/pingcap/failpoint"
)
func main() {
// 注入一个简单的故障点
failpoint.Inject("simple-failpoint", func(val failpoint.Value) {
fmt.Println("故障点触发,值:", val)
})
// 注入一个会panic的故障点
failpoint.Inject("panic-failpoint", func() {
panic("模拟panic故障")
})
// 注入带上下文的故障点
ctx := context.Background()
failpoint.InjectContext(ctx, "context-failpoint", func(val failpoint.Value) {
fmt.Println("带上下文的故障点,值:", val)
})
fmt.Println("程序正常执行")
}
故障点激活方式
可以通过环境变量激活故障点:
# 激活简单故障点并返回值100
GO_FAILPOINTS="main/simple-failpoint=return(100)" ./your-program
# 激活panic故障点
GO_FAILPOINTS="main/panic-failpoint=panic" ./your-program
# 激活带延迟的故障点
GO_FAILPOINTS="main/context-failpoint=sleep(1000)" ./your-program
设计原则
- 在有效的Golang代码中定义故障点,而不是注释或其他方式
- 故障点没有额外开销
- 不会影响常规逻辑
- 不会导致常规代码性能下降
- 故障点代码不会出现在最终二进制文件中
- 故障点例程可写/可读,并且应该由编译器检查
- 由故障点定义生成的代码易于阅读
- 保持与注入代码相同的行号(便于调试)
- 支持带有context.Context的并行测试
关键概念
故障点(Failpoint): 故障点是只有当相应故障点激活时才会执行的代码片段。如果执行了failpoint.Disable("failpoint-name-for-demo")
,闭包将永远不会被执行。
var outerVar = "declare in outer scope"
failpoint.Inject("failpoint-name-for-demo", func(val failpoint.Value) {
fmt.Println("unit-test", val, outerVar)
})
标记函数(Marker functions):
- 它是一个空函数
- 用于提示重写器用相等语句进行重写
- 用于接收一些参数作为重写规则
- 它将在编译时内联,不会向二进制文件发出任何内容(零成本)
- 通过捕获可以在闭包中访问外部范围内的变量,并且转换后的代码仍然合法,因为所有捕获变量都位于IF语句的外部范围内
支持的故障点环境变量: 故障点可以通过导出的环境变量启用,模式如下:
[<percent>%][<count>*]<type>[(args...)][-><more terms>]
支持的参数类型:
- off: 不采取任何措施(不触发故障点代码)
- return: 用指定参数触发故障点
- sleep: 睡眠指定的毫秒数
- panic: 引发panic
- break: 执行gdb并进入调试器
- print: 打印注入变量的故障点路径
- pause: 暂停直到故障点被禁用
更多关于golang实现代码故障注入与测试的failpoint插件使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang实现代码故障注入与测试的failpoint插件使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 故障注入与测试:failpoint 插件使用指南
什么是故障注入
故障注入(Fault Injection)是一种在系统中故意引入错误以测试其容错能力的测试技术。在Golang中,我们可以使用failpoint
插件来实现这一功能。
failpoint 简介
failpoint
是PingCAP开源的一个Golang故障注入工具,它允许你在代码中注入各种故障场景,如panic、延迟、错误返回等,而无需修改生产代码。
安装 failpoint
go get github.com/pingcap/failpoint
基本使用示例
1. 标记故障注入点
package main
import (
"fmt"
"time"
"github.com/pingcap/failpoint"
)
func processRequest() error {
// 标记一个故障注入点
failpoint.Inject("process-failure", func() error {
return fmt.Errorf("injected failure")
})
// 正常业务逻辑
fmt.Println("Processing request...")
time.Sleep(100 * time.Millisecond)
return nil
}
2. 启用故障注入
func main() {
// 启用故障注入
failpoint.Enable("process-failure", "return('injected error')")
err := processRequest()
if err != nil {
fmt.Printf("Error: %v\n", err)
}
// 禁用故障注入
failpoint.Disable("process-failure")
}
常用故障注入类型
1. 返回错误
failpoint.Inject("db-error", func() error {
return fmt.Errorf("database connection failed")
})
2. 注入延迟
failpoint.Inject("slow-query", func() error {
time.Sleep(2 * time.Second)
return nil
})
3. 随机故障
failpoint.Inject("random-failure", func() error {
if rand.Intn(100) < 30 { // 30%概率失败
return fmt.Errorf("random failure")
}
return nil
})
4. 条件触发
failpoint.Inject("conditional-failure", func(val int) error {
if val > 100 {
return fmt.Errorf("value too large")
}
return nil
})
高级用法
1. 链式调用
failpoint.Inject("chain-failure", func() error {
return failpoint.Inject("nested-failure", func() error {
return fmt.Errorf("nested error")
})
})
2. 概率控制
failpoint.Enable("probabilistic-failure", "30%return('failed')")
3. 动态修改
// 运行时动态修改故障行为
failpoint.Modify("dynamic-failure", "50%return('partial failure')|50%return('complete failure')")
测试中的使用
func TestProcessRequest_Failure(t *testing.T) {
// 设置故障注入
failpoint.Enable("process-failure", "return('injected error')")
defer failpoint.Disable("process-failure")
err := processRequest()
if err == nil {
t.Error("Expected error but got nil")
}
if err.Error() != "injected error" {
t.Errorf("Expected 'injected error' but got %v", err)
}
}
最佳实践
- 明确命名:给故障注入点起有意义的名称,如"db-connection-failure"
- 文档记录:记录代码中的所有故障注入点
- 测试覆盖:为每个故障场景编写测试用例
- 生产禁用:确保生产环境中所有故障注入都被禁用
- 渐进式:从简单故障开始,逐步增加复杂度
注意事项
- 故障注入只应在测试环境中使用
- 注入点不应影响代码的主要逻辑路径
- 避免在性能关键路径上使用故障注入
- 确保测试后清理所有注入点
通过合理使用failpoint,你可以大大提高代码的健壮性和容错能力测试覆盖率。