Go withinterfacesでのユニットテスト

紹介する代わりに


この記事は、私のように、Djangoの世界からGoに来た人を対象としています。だから、ジャンゴは私たちを台無しにしました。彼自身が内部でテストデータベースを作成し、移行を実行し、実行後にクリーンアップするため、テストを実行するだけで済みます。便利ですか?もちろん。移行を実行するのに時間がかかるというだけです-キャリッジですが、これは快適さのためのリーズナブルな価格のようです、そして常にあります--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.



, , .





もちろん、このアプローチには欠点があります。たとえば、ロジックが何らかの内部データベースロジックに関連付けられている場合、そのようなテストでは、データベースによって引き起こされたエラーを明らかにすることはできません。しかし、データベースとサードパーティのサービスを利用したテストは、もはやユニットテストではなく、より統合されたテスト、さらにはe2eテストであり、この記事の範囲をいくらか超えていると思います。



テストの読み取りと書き込みをありがとう!




All Articles