Pengujian unit di Go dengan antarmuka

Alih-alih memperkenalkan


Artikel ini untuk mereka yang, seperti saya, datang ke Go dari dunia Django. Nah, Django memanjakan kami. Seseorang hanya perlu menjalankan tes, karena dia sendiri, di bawah tenda, akan membuat database tes, menjalankan migrasi, dan setelah menjalankan akan membersihkan dirinya sendiri. Nyaman? Pasti. Tapi butuh waktu untuk menjalankan migrasi - gerbong, tapi sepertinya harga yang wajar untuk kenyamanan, plus selalu ada--reuse-db... Kejutan budaya semakin intens ketika Jungler berpengalaman datang ke bahasa lain, seperti Go. Artinya, bagaimana mungkin tidak ada otomatisasi sebelum dan sesudah? Tangan? Dan dasarnya? Tangan juga? Dan setelah tes? Apa, dan tirdown dengan tanganmu? Dan kemudian programmer, menyelingi kode dengan terengah-engah dan mendesah, mulai menulis junga di Go dalam proyek terpisah. Tentu saja semuanya terlihat sangat menyedihkan. Namun, di Go sangat mungkin untuk menulis pengujian unit yang cepat dan andal tanpa menggunakan layanan pihak ketiga seperti database pengujian atau cache.



Ini akan menjadi ceritaku.



Apa yang kami uji?


Mari kita bayangkan bahwa kita perlu menulis sebuah fungsi yang memeriksa keberadaan seorang pegawai di database dengan nomor telepon.



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
}


Oke, tulis mereka. Bagaimana cara mengujinya? Anda dapat, tentu saja, membuat database pengujian sebelum menjalankan pengujian, membuat tabel di dalamnya, dan setelah menjalankan database ini, secara perlahan merusaknya.



Tetapi ada cara lain.



Antarmuka


, , , 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.



, , .





Tentu saja, pendekatan ini memiliki kekurangan. Misalnya, jika logika Anda terkait dengan beberapa jenis logika database internal, maka pengujian tersebut tidak akan dapat mengidentifikasi kesalahan yang disebabkan oleh database. Tetapi saya percaya bahwa pengujian dengan partisipasi database dan layanan pihak ketiga tidak lagi tentang pengujian unit, ini lebih merupakan integrasi atau bahkan pengujian e2e, dan mereka agak di luar cakupan artikel ini.



Terima kasih telah membaca dan menulis tes!




All Articles