## 使用标准库进行模拟
在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)
- 前言
- 第一章 I/O和文件系统
- 常见 I/O 接口
- 使用bytes和strings包
- 操作文件夹和文件
- 使用CSV格式化数据
- 操作临时文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag标识
- 解析命令行参数
- 读取和设置环境变量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系统下的pipe管道
- 处理信号量
- ANSI命令行着色
- 第三章 数据类型转换和解析
- 数据类型和接口转换
- 使用math包和math/big包处理数字类型
- 货币转换和float64注意事项
- 使用指针和SQL Null类型进行编码和解码
- 对Go数据编码和解码
- Go中的结构体标签和反射
- 通过闭包实现集合操作
- 第四章 错误处理
- 错误接口
- 使用第三方errors包
- 使用log包记录错误
- 结构化日志记录
- 使用context包进行日志记录
- 使用包级全局变量
- 处理恐慌
- 第五章 数据存储
- 使用database/sql包操作MySQL
- 执行数据库事务接口
- SQL的连接池速率限制和超时
- 操作Redis
- 操作MongoDB
- 创建存储接口以实现数据可移植性
- 第六章 Web客户端和APIs
- 使用http.Client
- 调用REST API
- 并发操作客户端请求
- 使用OAuth2
- 实现OAuth2令牌存储接口
- 封装http请求客户端
- 理解GRPC的使用
- 第七章 网络服务
- 处理Web请求
- 使用闭包进行状态处理
- 请求参数验证
- 内容渲染
- 使用中间件
- 构建反向代理
- 将GRPC导出为JSON API
- 第八章 测试
- 使用标准库进行模拟
- 使用Mockgen包
- 使用表驱动测试
- 使用第三方测试工具
- 模糊测试
- 行为驱动测试
- 第九章 并发和并行
- 第十章 分布式系统
- 第十一章 响应式编程和数据流
- 第十二章 无服务器编程
- 第十三章 性能改进