使用标准库进行模拟
最后更新于:2022-04-02 06:48:50
## 使用标准库进行模拟
在Go中,模拟通常意味着实现具有测试版本的接口,该测试版本允许从测试中控制运行时行为。它也可以指模拟函数和方法,我们将探索如何实现它。示例中使用的Patch和Restore函数可以在https://play.golang.org/p/oLF1XnRX3C 找到。
包含大量分支条件或深度嵌套逻辑的代码可能很难测试,最后测试往往更加效果很差。这是因为开发人员需要在其测试中跟踪很多模拟对象,返回值和状态。
### 实践
1. 建立 mock.go:
```
package mocking
// DoStuffer 是一个简单的接口
type DoStuffer interface {
DoStuff(input string) error
}
```
2. 建立 patch.go:
```
package mocking
import "reflect"
// Restorer是一个可用于恢复先前状态的函数
type Restorer func()
// Restore存储了之前的状态
func (r Restorer) Restore() {
r()
}
// Patch将给定目标指向的值设置为给定值,并返回一个函数以将其恢复为原始值。 该值必须可分配给目标的元素类型。
func Patch(dest, value interface{}) Restorer {
destv := reflect.ValueOf(dest).Elem()
oldv := reflect.New(destv.Type()).Elem()
oldv.Set(destv)
valuev := reflect.ValueOf(value)
if !valuev.IsValid() {
// 对于目标类型不可用的情况,这种解决方式并不优雅
valuev = reflect.Zero(destv.Type())
}
destv.Set(valuev)
return func() {
destv.Set(oldv)
}
}
```
3. 建立 exec.go:
```
package mocking
import "errors"
var ThrowError = func() error {
return errors.New("always fails")
}
func DoSomeStuff(d DoStuffer) error {
if err := d.DoStuff("test"); err != nil {
return err
}
if err := ThrowError(); err != nil {
return err
}
return nil
}
```
4. 建立 mock_test.go:
```
package mocking
type MockDoStuffer struct {
// 使用闭包模拟
MockDoStuff func(input string) error
}
func (m *MockDoStuffer) DoStuff(input string) error {
if m.MockDoStuff != nil {
return m.MockDoStuff(input)
}
// 如果我们不模拟输入,就返回一个常见的情况
return nil
}
```
5. 建立 exec_test.go:
```
package mocking
import (
"errors"
"testing"
)
func TestThrowError(t *testing.T) {
tests := []struct {
name string
wantErr bool
}{
{"base-case", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ThrowError(); (err != nil) != tt.wantErr {
t.Errorf("DoSomeStuff() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestDoSomeStuff(t *testing.T) {
tests := []struct {
name string
DoStuff error
ThrowError error
wantErr bool
}{
{"base-case", nil, nil, false},
{"DoStuff error", errors.New("failed"), nil, true},
{"ThrowError error", nil, errors.New("failed"), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 使用模拟结构来模拟接口
d := MockDoStuffer{}
d.MockDoStuff = func(string) error { return tt.DoStuff }
// 模拟声明为变量的函数对func A()不起作用,必须是var A = func()
defer Patch(&ThrowError, func() error { return tt.ThrowError }).Restore()
if err := DoSomeStuff(&d); (err != nil) != tt.wantErr {
t.Errorf("DoSomeStuff() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
```
6. 运行go test:
```
PASS
ok github.com/agtorre/go-cookbook/chapter8/mocking 0.006s
```
### 说明
无论是使用errors.New,fmt.Errorf还是自定义错误,最重要的是不应该在代码中不处理错误。这些定义错误的不同方法提供了很大的灵活性。例如,你可以在结构中添加额外的函数,以进一步检查错误并将接口转换为调用函数中的错误类型,以获得一些额外的功能。
接口本身非常简单,唯一的要求是返回一个有效的字符串。(在测试中明显将其复杂化了)这样的测试保证对某些要求严格的应用程序同样可用。
* * * *
学识浅薄,错误在所难免。欢迎在群中就本书提出修改意见,以飨后来者,长风拜谢。
Golang中国(211938256)
beego实战(258969317)
Go实践(386056972)
';