简介
良好的单元测试不仅可以在代码发布前验证代码是否可用,并且可以保护代码在以后的某一次修改中功能不被破坏。好的单元测试需要有自我隔离、可靠、无状态等特性。以下介绍五种 mock 技术。
1. Higher-Order Functions
当需要 mock 一个包级别函数的时候使用。
以下代码打开一个数据库连接:
func OpenDB(user, password, addr, db string) (*sql.DB, error) {
conn := fmt.Sprintf("%s:%s@%s/%s", user, password, addr, db)
return sql.Open("mysql", conn)
}
为了 mock 出 sql.Open
,可以这样做:
type (
// 将 sql 包的 sql.Open 函数保存在一个变量
sqlOpener func(string, string) (*sql.DB, error)
)
// 现在调用 OpenDB 需要将 sql.Open 传入
func OpenDB(user, password, addr, db string, open sqlOpener) (*sql.DB, error) {
conn := fmt.Sprintf("%s:%s@%s/%s", user, password, addr, db)
return open("mysql", conn)
}
将直接在函数 OpenDB
调用 sql.Open
转化为:将 sqlOpener
函数作为入参。
在源码中只需将 sql.Open
函数显式传入,如下:
OpenDB(“myUser”, “myPass”, “localhost”, “foo”, sql.Open)
在测试时,可以这样 mock 出该 sql.Open
函数:
func TestOpenDB(t *testing.T) {
mockError := errors.New("uh oh")
subtests := []struct {
name string
u, p, a, db string
sqlOpener func(string, string) (*sql.DB, error)
expectedErr error
}{
{
name: "happy path",
u: "u",
p: "p",
a: "a",
db: "db",
// mock 出 sql.Open 函数,传入 OpenDB 方法。
sqlOpener: func(s string, s2 string) (db *sql.DB, err error) {
if s != "u:p@a/db" {
return nil, errors.New("wrong connection string")
}
return nil, nil
},
},
{
name: "error from sqlOpener",
sqlOpener: func(s string, s2 string) (db *sql.DB, err error