go 单元测试
单元测试作用
上家公司,由于项目过多依赖于服务器环境,在本地压根跑不起来,所以从不写单元测试。后来换了家做业务的公司,逐渐意识到单元测试的重要性。写了一周多的单元测试。好处总结有两点
- 提前发现和拦截低级 bug,减少后期测试成本
- 重构或迭代后,进行归回测试,防止
改出bug来 - 作为
CI/CD第一环节
单元测试用例
定义一个方法
func Split(s, sep string) (result []string) {
i := strings.Index(s, sep)
for i > -1 {
result = append(result, s[:i])
s = s[i+len(sep):] // 这里使用len(sep)获取sep的长度
i = strings.Index(s, sep)
}
result = append(result, s)
return
}
表格驱动测试
// 表格驱动测试
func TestList(t *testing.T) {
type test struct {
input string
sep string
want []string
}
tests := map[string]test{ // 测试用例使用map存储
"simple": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
"wrong sep": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
"more sep": {input: "abcd", sep: "bc", want: []string{"a", "d"}},
"leading sep": {input: "枯藤老树昏鸦", sep: "老", want: []string{"枯藤", "树昏鸦"}},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("want: %+v, got: %+v;", tc.want, got)
}
})
}
}
运行
go test -v ./...
知识点总结
t.Run()是go推出的子测试;如果使用传统方法,第一个case失败,后面的case就不会跑了- 报错能直接告诉你,是哪个
case错了 - 单独运行某个函数:
go test -v ./spilt/... -run=TestList - 单独运行某个 case:
go test -v ./spilt/... -run=TestList/leading_sep
使用 testify mock 数据
原始方法
type User struct {
ID int64
Name string
}
type UserRepo interface {
GetByID(ctx context.Context, id int64) (*User, error)
}
type UserService struct {
repo UserRepo
}
func NewUserService(repo UserRepo) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) GetUserName(ctx context.Context, id int64) (string, error) {
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return "", err
}
return user.Name, nil
}
测试方法
package d2
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockUserRepo struct {
mock.Mock // 这就是剧本,接下来就用它来演戏
}
func (m *MockUserRepo) GetByID(ctx context.Context, id int64) (*User, error) {
//记录一次调用 + 从预设的返回值仓库里,按参数匹配拿结果
args := m.Called(ctx, id)
var user *User
if args.Get(0) != nil {
user = args.Get(0).(*User)
}
return user, args.Error(1)
}
func TestUserService_GetUserName(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
id int64
mockSetup func(repo *MockUserRepo)
wantName string
wantErr bool
}{
{
name: "success",
id: 1,
//定义 mock 的入参和出参
mockSetup: func(repo *MockUserRepo) {
repo.On(
"GetByID",
mock.Anything,
int64(1),
).Return(
&User{ID: 1, Name: "Tom"},
nil,
)
},
wantName: "Tom",
wantErr: false,
},
{
name: "repo error",
id: 2,
mockSetup: func(repo *MockUserRepo) {
repo.On(
"GetByID",
mock.Anything,
int64(2),
).Return(
nil,
errors.New("db error"),
)
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := new(MockUserRepo)
tt.mockSetup(repo)
svc := NewUserService(repo)
name, err := svc.GetUserName(ctx, tt.id)
if tt.wantErr {
assert.Error(t, err)
assert.Empty(t, name)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantName, name)
}
repo.AssertExpectations(t)
})
}
}
浙公网安备 33010602011771号