Golang 单元测试介绍
单元测试面向的是函数或方法,需要开发人员针对每个函数或方法,编写一组或几组测试用例,要求尽可能包含所有可能的情况。
在 Golang 中,如果要对函数 func GetApp() 进行单元测试,那么对应的测试函数应该命名为 func TestGetApp()。单元测试函数应该在一个新的文件中,命名为 xx_test.go,与代码文件逐个相对应。
为函数或方法打桩:每个单元测试函数或方法需要不受网络、数据库连接的影响,能够在本地独立运行。因此需要替换掉原来代码中函数或方法中关于网络连接、数据库连接相关的逻辑代码,让单元测试执行一个假的网络、数据库连接。
为全局变量打桩:函数或方法的执行可能会依赖全局变量,无法独立运行。可以在单元测试函数或方法中为依赖的全局变量打桩,让函数或方法能独立运行。
gomonkey 支持为函数打桩、为方法打桩、为函数序列打桩、为方法序列打桩、为全局变量打桩。对于参数比较复杂的函数或方法,本人更倾向于使用序列打桩的方式,可以免去写复杂的参数。
goconvey 可以嵌套使用,一般一个测试用例位于一个 Convey 中。
sqlmock 对sql语句进行正则匹配,无需 一个一个函数地 mock(例如 Select...Find...)。
单元测试样例1-为函数打桩
func GetCustomer() ([]Customer, error) {
var customers []Customer
err := proxy.GetMysqlClient().Select("customer_name").Find(&customers).Error
return customers, err
}
为了屏蔽数据库连接,可以使用为函数打桩或为函数序列打桩。proxy.GetMockMysqlClient 是把数据库的连接换成替换后的虚拟数据库连接。
var mockOnce sync.Once
var mockMysqlClient *gorm.DB
func GetMockMysqlClient(db *sql.DB) *gorm.DB {
mockOnce.Do(func() {
var err error
mockMysqlClient, err = gorm.Open(
mysql.New(mysql.config{SkipInitializeWithVersion: true, Conn:db}),
&gorm.config{}
)
})
}
func TestGetCustomer() (t *testing.T) {
db, mock, err := sqlmock.New()
So(err, ShouldBeNil)
p := ApplyFunc(proxy.GetMysqlClient, func() *gorm.DB){ // func() 模拟函数GetMysqlClient的参数以及返回值
return proxy.GetMockMysqlClient(db)
}
defer p.Reset()
defer db.Close()
rows := sqlmock.NewRows([]string{"customer_name"}).AddRow("123)
mock.ExpectQuery("SELECT `customer_name` FROM `customer`").WillReturnRows(rows)
var res []Customer
res, err := GetCustomer()
So(err, nil)
So(res[0].CustomerName, ShouldEqual, "123")
}
单元测试样例2-为函数序列打桩
func TestGetCustomerHandler() (t *testing.T) {
Convey("test GetCustomerHander", t, func() {
outputs := OutputCell{ // 定义函数的输出
{Values: Param{[]Customer{{}}}}
}
p := ApplyFuncSeq(dao.GetCustomers, outputs) // 无需模拟参数和返回值,直接定义函数的输出
defer p.Reset()
if res, ok := CustomerHandler(); ok {
So(res.code, ShouldEqual, 200)
}
})
}