Golang单元测试(gomonkey+goconvey+sqlmock)

3,447 阅读2分钟

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)
        }
    })
}