最近了解了一些关于go failpoint的信息,现在想给大家分享出来。
FailPiont 是用来在自动化测试中模拟故障的
自动化测试中,经常需要模拟出一些故障情况,然后来测试我们的程序在这种故障情况下是否能按照我们的想法正常执行。一些故障比较容易的被测试代码模拟,但是有一些就相对困难,比如断网了,接口访问量超限了等。于是有大神就搞出来了FailPiont。
Etcd做出了 gofail https://github.com/etcd-io/gofail
gofail 是这样使用的
func someFunc() string { // gofail: var SomeFuncString string // // this is SomeFuncStringcalled when the failpoint is triggered // return SomeFuncString return "default" }
先写一段模拟故障的代码,然后把代码注释掉。在我们测试的时候,执行 gofail enable 命令,然后代码就成了这样
func someFunc() string { if vSomeFuncString, __fpErr := __fp_SomeFuncString.Acquire(); __fpErr == nil { defer __fp_SomeFuncString.Release(); SomeFuncString, __fpTypeOK := vSomeFuncString.(string); if !__fpTypeOK { goto __badTypeSomeFuncString} // this is SomeFuncStringcalled when the failpoint is triggered return SomeFuncString; __badTypeSomeFuncString: __fp_SomeFuncString.BadType(vSomeFuncString, "string"); }; return "default" }
还有一个生成的文件
// GENERATED BY GOFAIL. DO NOT EDIT. package fail_point import "github.com/etcd-io/gofail/runtime" var __fp_SomeFuncString *runtime.Failpoint = runtime.NewFailpoint("fail_point", "SomeFuncString")
然后我们再写一个测试代码
func Test_someFunc(t *testing.T) { // /Users/edz/Documents/dev/metrics/src/fail_point/ gofail.Enable("fail_point/SomeFuncString", `return("SomeFuncString")`) defer gofail.Disable("fail_point/SomeFuncString") tests := []struct { name string want string }{ {name: "pass", want: "SomeFuncString"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := someFunc(); got != tt.want { t.Errorf("someFunc() = %v, want %v", got, tt.want) } }) } }
跑一下测试,通过了。
但是这个gofail也有一些弊端的 ,比如是以注释存在,不易发现错误;而且生成的代码可读性很差。
为了解决这些问题,pingcap公司做出了一个升级版的gofail==》failpiont
failpiont的代码是这样的
func pingCapFail() (string, failpoint.Value) { failpoint.Inject("failpoint-name", func(val failpoint.Value) { failpoint.Return("unit-test", val) }) return "success", nil } func pingCapFail1() string { failpoint.Inject("failpoint-name1", func() { failpoint.Return("unit-test") }) return "success" } func pingCapFail2(ctx context.Context) (string, failpoint.Value) { failpoint.InjectContext(ctx, "failpoint-name2", func(val failpoint.Value) { failpoint.Return("unit-test", val) }) return "success", nil }
我们可以看到,failpiont避免了使用注释来实现的弊端,而且可以注入一定的代码,来影响代码的执行。
这样就可以在测试的时候,多个测试用例同时进行,不同用例有的会就触发故障,有的则会忽略故障。
执行 failpoint-ctl enable 命令来启用
这时候代码变成了这样
func pingCapFail() (string, failpoint.Value) { if val, ok := failpoint.Eval(_curpkg_("failpoint-name")); ok { return "unit-test", val } return "success", nil } func pingCapFail1() string { if _, ok := failpoint.Eval(_curpkg_("failpoint-name1")); ok { return "unit-test" } return "success" } func pingCapFail2(ctx context.Context) (string, failpoint.Value) { if val, ok := failpoint.EvalContext(ctx, _curpkg_("failpoint-name2")); ok { return "unit-test", val } return "success", nil }
可以看到这时候的代码的可读性也很高。
然后我们写一些测试代码
func Test_pingCapFail(t *testing.T) { tests := []struct { name string want string want1 failpoint.Value }{ {name: "pingCapFail", want: "unit-test", want1: 5}, } failpoint.Enable("fail_point/failpoint-name", "return(5)") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := pingCapFail() if got != tt.want { t.Errorf("pingCapFail() got = %v, want %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { t.Errorf("pingCapFail() got1 = %v, want %v", got1, tt.want1) } }) } } func Test_pingCapFail1(t *testing.T) { tests := []struct { name string want string }{ {name: "pingCapFail1", want: "unit-test"}, } failpoint.Enable("fail_point/failpoint-name1", "return(5)") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := pingCapFail1(); got != tt.want { t.Errorf("pingCapFail1() = %v, want %v", got, tt.want) } }) } } func Test_pingCapFail2(t *testing.T) { type args struct { ctx context.Context } c := &gin.Context{} failPoints := map[string]struct{}{ "a": {}, "b": {}, // "fail_point/failpoint-name2": {}, } ctx := failpoint.WithHook(c, func(ctx context.Context, fpname string) bool { _, found := failPoints[fpname] // Only enables some failpoints. return found }) tests := []struct { name string args args want string want1 failpoint.Value }{ {"pingCapFail2", args{ctx}, "success", nil}, } failpoint.Enable("fail_point/failpoint-name2", "return(5)") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := pingCapFail2(tt.args.ctx) if got != tt.want { t.Errorf("pingCapFail2() got = %v, want %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { t.Errorf("pingCapFail2() got1 = %v, want %v", got1, tt.want1) } }) } }
运行一下,测试通过。
希望对大家有用,谢谢。