
























这是在做 Golang 项目中的一些实践

ginkgo 基于BDD的测试框架; 开始前, 需要花半个小时阅读官方文档
一个函数
// TruncateString truncate string to specific length
func TruncateString(s string, n int) string {
if n > len(s) {
return s
}
return s[:n]
}
使用table-driven方式生成测试代码
func TestTruncateString(t *testing.T) {
type args struct {
s string
n int
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := util.TruncateString(tt.args.s, tt.args.n); got != tt.want {
t.Errorf("TruncateString() = %v, want %v", got, tt.want)
}
})
}
}
如果是简单的util函数, 用例比较简单并且用例数量较少, 此时单元测试可读性和可维护性没有问题
当涉及业务相关的单元测试, 此时
由于table-driven的表达能力有限:
name过于简单不被重视, 导致单元测试失败时难以快速阅读最终带来的问题是:
难以阅读/难以理解/难以维护
优势:
注意, 实践中:
Describe和Context描述/表示行为和逻辑, 以及层次结构It 描述及放置每一个用例BeforeEach构建每个用例(It)用到的公共内容(setup); 利用AfterEach执行回收动作(teardown). 此时BeforeEach用到闭包, 构建闭包变量/类型声明等, 在每个It中可以复用到请先阅读官方文档之后, 再开始阅读以下内容
使用场景: util类的简单测试, input和expect都比较简单
使用
package util_test
import (
"fmt"
"strconv"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
"github.com/stretchr/testify/assert"
"example/pkg/util"
)
var _ = Describe("String", func() {
Describe("TruncateBytes", func() {
var s = []byte("helloworld")
DescribeTable("TruncateBytes cases", func(expected []byte, truncatedSize int) {
assert.Equal(GinkgoT(), expected, util.TruncateBytes(s, truncatedSize))
},
Entry("truncated size less than real size", []byte("he"), 2),
Entry("truncated size equals to real size", s, 10),
Entry("truncated size greater than real size", s, 20),
)
})
})
使用场景: 复杂的输入
Extracting Common Setup: BeforeEach
简单复用
Describe("WithoutResourceType", func() {
var a types.Action
BeforeEach(func() {
a = types.NewAction()
})
It("true", func() {
assert.True(GinkgoT(), a.WithoutResourceType())
})
It("true, empty ResourceType", func() {
a.Attribute.SetResourceTypes([]types.ActionResourceType{})
assert.True(GinkgoT(), a.WithoutResourceType())
})
It("false", func() {
a.Attribute.SetResourceTypes([]types.ActionResourceType{
{
System: "test",
Type: "test",
},
})
assert.False(GinkgoT(), a.WithoutResourceType())
})
}
复杂的复用
Describe("GetPolicesAttrKeys", func() {
var resource *types.Resource
var policies []types.AuthPolicy
BeforeEach(func() {
resource = &types.Resource{
System: "test",
Type: "host",
ID: "1",
Attribute: nil,
}
policies = []types.AuthPolicy{}
})
It("fail", func() {
errExpr := `[{"system": "test", "type": "host", "expression":
{"OR": {"content": [{"NotExists": {"id": []}}]}}}]`
policies = []types.AuthPolicy{
{
Expression: errExpr,
},
}
_, err := GetPolicesAttrKeys(resource, policies)
assert.Error(GinkgoT(), err)
})
})
with patch
Describe("FillSubjectDetail", func() {
var r *request.Request
var ctl *gomock.Controller
var patches *gomonkey.Patches
BeforeEach(func() {
ctl = gomock.NewController(GinkgoT())
r = request.NewRequest()
})
AfterEach(func() {
ctl.Finish()
if patches != nil {
patches.Reset()
}
})
It("pip.GetSubjectPK fail", func() {
patches = gomonkey.ApplyFunc(pip.GetSubjectPK, func(_type, id string) (pk int64, err error) {
return -1, errors.New("get subject_pk fail")
})
err := r.FillSubjectDetail()
assert.Error(GinkgoT(), err)
assert.Contains(GinkgoT(), err.Error(), "get subject_pk fail")
})
})
bootstrap & generate
# 进入模块
$ cd pkg/util
# 生成一个模块的 {module_name}_suite_test.go
$ ginkgo bootstrap
# 生成模块下某个文件 {file_name}_test.go
$ ginkgo generate set
run
# 运行当前目录下的
$ ginkgo
# 带tags和gcflags (如果做了mock)
$ ginkgo -tags=jsoniter -gcflags=all=-l
# 递归执行当前目录及所有子目录下的
$ ginkgo -r -v -cover
# 只执行某几个目录
$ ginkgo -r pkg/util pkg/cache
确认覆盖率 make test && make cov
# in Makefile
test:
go test -mod=vendor -gcflags=all=-l $(shell go list ./... | grep -v mock | grep -v docs) -covermode=count -coverprofile .coverage.cov
cov:
go tool cover -html=.coverage.cov
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。