而不是介绍
本文适用于那些像我一样来自Django世界的人。因此,Django宠坏了我们。一个人只需要运行测试,因为他本人将在后台创建一个测试数据库,运行迁移,运行之后将自己清理。方便吗 当然。只是运行迁移需要时间-一辆马车,但这似乎是个合理的舒适价格,而且总会有--reuse-db
... 当经验丰富的丛林人使用其他语言(例如Go)时,文化冲击就更加强烈。也就是说,前后如何没有自动迁移?手?和基地?手呢?测试之后呢?什么,用手翻倒?好吧,然后,程序员在叹息中穿插了代码,开始在单独的项目中用Go编写jungu。当然,这一切看起来都很难过。但是,在Go中,无需使用诸如测试数据库或缓存之类的第三方服务,就可以编写快速而可靠的单元测试。
这将是我的故事。
我们正在测试什么?
假设我们需要编写一个函数,该函数通过电话号码检查数据库中员工的存在。
func CheckEmployee(db *sqlx.DB, phone string) (error, bool) {
err := db.Get(`SELECT * FROM employees WHERE phone = ?`, phone)
if err != nil {
return err, false
}
return nil, true
}
好吧,他们写道。如何测试呢?当然,您可以在运行测试之前创建测试数据库,在其中创建表,然后在运行该数据库之后将其轻轻崩溃。
但是还有另一种方式。
介面
, , , Get
. , -, , , , , , .
. Go? , — -, , , , , . , ?
.
:
type ExampleInterface interface {
Method() error
}
, , :
type ExampleStruct struct {}
func (es ExampleStruct) Method() error {
return nil
}
, ExampleStruct
ExampleInterface
, , - ExampleInterface
, ExampleStruct
.
?
, Get
, , , , , Get
sqlx.Get
.
Talk is cheap, let's code!
:
Get(dest interface{}, query string, args ...interface{}) error
, Get
:
type BaseDBClient interface {
Get(interface{}, string, ...interface{}) error
}
:
func CheckEmployee(db BaseDBClient, phone string) (err error, exists bool) {
var employee interface{}
err = db.Get(&employee, `SELECT name FROM employees WHERE phone = ?`, phone)
if err != nil {
return err, false
}
return nil, true
}
, , , , sqlx.Get
, sqlx
, , BaseDBClient
.
, .
, , .
, BaseDBClient
:
type TestDBClient struct {}
func (tc *TestDBClient) Get(interface{}, string, ...interface{}) error {
return nil
}
, , , , , , , .
, — CheckEmployee
:
func TestCheckEmployee() {
test_client := TestDBClient{}
err, exists := CheckEmployee(&test_client, "nevermind")
assert.NoError(t, err)
assert.Equal(t, exists, true)
}
, . , , :
type BaseDBClient interface {
Get(interface{}, string, ...interface{}) error
}
type TestDBClient struct {
success bool
}
func (t *TestDBClient) Get(interface{}, string, ...interface{}) error {
if t.success {
return nil
}
return fmt.Errorf("This is a test error")
}
func TestCheckEmployee(t *testing.T) {
type args struct {
db BaseDBClient
}
tests := []struct {
name string
args args
wantErr error
wantExists bool
}{
{
name: "Employee exists",
args: args{
db: &TestDBClient{success: true},
},
wantErr: nil,
wantExists: true,
}, {
name: "Employee don't exists",
args: args{
db: &TestDBClient{success: false},
},
wantErr: fmt.Errorf("This is a test error"),
wantExists: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr, gotExists := CheckEmployee(tt.args.db, "some phone")
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("CheckEmployee() gotErr = %v, want %v", gotErr, tt.wantErr)
}
if gotExists != tt.wantExists {
t.Errorf("CheckEmployee() gotExists = %v, want %v", gotExists, tt.wantExists)
}
})
}
}
! , , , , , go.
, , .
当然,这种方法有其缺点。例如,如果您的逻辑与某种内部数据库逻辑相关联,则此类测试将无法识别由数据库引起的错误。但是我相信,在数据库和第三方服务的参与下进行的测试不再是关于单元测试的,而是单元测试,甚至是端到端测试,它们超出了本文的范围。
感谢您阅读和编写测试!