带有接口的Go中的单元测试

而不是介绍


本文适用于那些像我一样来自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.



, , .





当然,这种方法有其缺点。例如,如果您的逻辑与某种内部数据库逻辑相关联,则此类测试将无法识别由数据库引起的错误。但是我相信,在数据库和第三方服务的参与下进行的测试不再是关于单元测试的,而是单元测试,甚至是端到端测试,它们超出了本文的范围。



感谢您阅读和编写测试!




All Articles