测试类型
参考原文:软件测试分哪几种,它们的区别有哪些?
单元测试
- 编写单元测试文件应与需要被测试文件的同包以"_test"结尾
- 单元测试内的函数应以Test开始以被测试的函数名结尾,且被测试函数名首字母应大写
- 测试单元可为简单函数或模块函数
- 单元测试应保证分支独立、全面覆盖,测试单元密度足够小,函数单一职责
- 组成部分:输入、输出、测试单元、与期望的校对,测试单元又包括函数、接口、模块、复杂的聚合函数等。通过单元测试的输出再与期望输出进行校对,来验证代码的正确性。通过单元测试可以保证代码的质量,也可以在一定程度上提升效率,比如通过运行单元测试可以快速定位到有问题的代码。
以下为一个add函数的测试单元
// my_test.go
func TestAdd(t *testing.T) {
ans := add(12, 34)
exp := 46
if ans != exp {
t.Error("答案错误")
}
}
使用assert可简化为
func TestAdd(t *testing.T) {
assert.Equal(t, 46,add(12, 34), "答案错误")
}
Mock测试
mock测试目标是让用户在单元测试中低成本的完成打桩,mock通过"虚拟"代码替换掉依赖的方法和资源,减少对网络、文件或者数据库的依赖。
使用go-sqlmock对数据库进行Mock测试
核心目的
- 减小测试过程中对于数据库的依赖
- 使在测试中产生的数据内容不会对数据库产生影响
- 对于复杂逻辑应多进行测试,多编写测试样例保证函数中每一条sql、每一个if-else都能被执行,简单逻辑编写少量测试样例即可。
下载安装
go get github.com/DATA-DOG/go-sqlmock
mock数据库初始化
// roleServiceImpl_test.go
// ...
var (
mock sqlmock.Sqlmock
mockDB *sql.DB
)
func init() {
// 把匹配器设置成相等匹配器,不设置默认使用正则匹配
mockDB, mock, _ = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
// 配置mock数据库
gormDB, _ := gorm.Open(mysql.New(mysql.Config{
SkipInitializeWithVersion: true,
Conn: mockDB,
}), &gorm.Config{})
// 设置gen
gen.SetDefault(gormDB)
fmt.Println("mock数据库已连接")
}
使用goland生成测试模板
在需要测试的方法内部右键-生成/Generate 选择生成函数测试
生成的模板结构
func TestRoleServiceImpl_GetRole(t *testing.T) {
// 待测试函数所需传入的参数
type args struct {
id int64
}
// 全部测试样例点 一个测试函数可能需要多个样例点
tests := []struct {
name string
args args
want *model.Role
want1 *errno.Errno
}{
// TODO: Add test cases. 样例点需要自己编写
}
// 遍历全部样例点
for _, tt := range tests {
// 执行其中一个样例
t.Run(tt.name, func(t *testing.T) {
ro := RoleServiceImpl{}
// 执行待测试的函数
got, got1 := ro.GetRole(tt.args.id)
// 断言返回值是否与期望的返回值相同
assert.Equalf(t, tt.want, got, "GetRole(%v)", tt.args.id)
assert.Equalf(t, tt.want1, got1, "GetRole(%v)", tt.args.id)
})
}
}
编写测试样例
tests := []struct {
name string
args args
want *model.Role
want1 *errno.Errno
}{
{
name: "获取学生角色测试点", // 样例点名称
args: args{1}, // 样例点传入的函数参数
want: &model.Role{ // 期望的返回值
ID: 1,
Name: "学生",
IsDelete: 0,
},
want1: nil, // 期望的返回值
}, {
name: "获取专业管理员角色测试点",
args: args{3},
want: &model.Role{
ID: 3,
Name: "专业管理员",
IsDelete: 0,
},
want1: nil,
},
}
获取待替换的sql语句
方法:使用sqlmock替换一条空sql,然后选择一个一个样例点执行,根据报错信息找的需要替换的sql
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 替换空sql
mock.ExpectQuery("")
ro := RoleServiceImpl{}
got, got1 := ro.GetRole(tt.args.id)
assert.Equalf(t, tt.want, got, "GetRole(%v)", tt.args.id)
assert.Equalf(t, tt.want1, got1, "GetRole(%v)", tt.args.id)
})
}
执行一个样例
查看报错信息
可得知所需替换的sql为 SELECT * FROM `role` WHERE `role`.`id` = ? AND `role`.`is_delete` = ? LIMIT 1 最后再修改测试代码
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` = ? AND `role`.`is_delete` = ? LIMIT 1")
ro := RoleServiceImpl{}
got, got1 := ro.GetRole(tt.args.id)
assert.Equalf(t, tt.want, got, "GetRole(%v)", tt.args.id)
assert.Equalf(t, tt.want1, got1, "GetRole(%v)", tt.args.id)
})
}
补充所需替换的数据
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`id` = ? AND `role`.`is_delete` = ? LIMIT 1").
WithArgs(tt.args.id, 0). // 补充占位符缺失的数据
WillReturnRows(
sqlmock.NewRows([]string{"id", "name", "is_delete"}). // 补充sql返回的字段
AddRow(tt.want.ID, tt.want.Name, tt.want.IsDelete)) // 补充字段内容
got, got1 := GetUserService().GetRole(tt.args.id)
assert.Equalf(t, tt.want, got, "GetRole(%v)", tt.args.id)
assert.Equalf(t, tt.want1, got1, "GetRole(%v)", tt.args.id)
})
}
执行样例查看信息
对于查询列表的测试
没有找到好的处理方法,只能对数据进行分页处理,再进行测试
func TestRoleServiceImpl_GetRoleList(t *testing.T) {
tests := []struct {
name string
want *[]*model.Role
want1 *errno.Errno
}{
{
name: "获取角色列表测试点",
want: &[]*model.Role{
{ID: 1, Name: "学生", IsDelete: 0},
{ID: 2, Name: "教师", IsDelete: 0},
{ID: 3, Name: "专业管理员", IsDelete: 0},
{ID: 4, Name: "院管理员", IsDelete: 0},
{ID: 5, Name: "校管理员", IsDelete: 0},
{ID: 6, Name: "管理员", IsDelete: 0},
{ID: 7, Name: "超级管理员", IsDelete: 0},
},
want1: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 匹配查询sql 并模拟查询到的数据
mock.ExpectQuery("SELECT * FROM `role` WHERE `role`.`is_delete` = ?").
WithArgs(0).
WillReturnRows(
sqlmock.NewRows([]string{"id", "name", "is_delete"}).
AddRow((*tt.want)[0].ID, (*tt.want)[0].Name, (*tt.want)[0].IsDelete).
AddRow((*tt.want)[1].ID, (*tt.want)[1].Name, (*tt.want)[1].IsDelete).
AddRow((*tt.want)[2].ID, (*tt.want)[2].Name, (*tt.want)[2].IsDelete).
AddRow((*tt.want)[3].ID, (*tt.want)[3].Name, (*tt.want)[3].IsDelete).
AddRow((*tt.want)[4].ID, (*tt.want)[4].Name, (*tt.want)[4].IsDelete).
AddRow((*tt.want)[5].ID, (*tt.want)[5].Name, (*tt.want)[5].IsDelete).
AddRow((*tt.want)[6].ID, (*tt.want)[6].Name, (*tt.want)[6].IsDelete))
ro := RoleServiceImpl{}
got, got1 := ro.GetRoleList()
assert.Equalf(t, tt.want, got, "GetRoleList()")
assert.Equalf(t, tt.want1, got1, "GetRoleList()")
})
}
}
增删改的mock测试
应使用以下方法进行测试,update,delete同理
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock.ExpectBegin()
mock.ExpectExec("INSERT INTO `role` (`name`) VALUES (?) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`)").
WithArgs(tt.args.role.Name).
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
ro := RoleServiceImpl{}
assert.Equalf(t, tt.want, ro.AddRole(tt.args.role), "AddRole(%v)", tt.args.role)
})
}