
Tidak ada yang suka menulis tes. Tentu saja aku bercanda, semua orang suka menulisnya! Seperti yang akan disampaikan oleh pemimpin tim dan SDM, jawaban yang tepat dalam wawancara adalah bahwa saya benar-benar menyukai dan menulis ujian. Tapi tiba-tiba Anda suka menulis tes dalam bahasa lain. Bagaimana Anda mulai menulis kode go yang tercakup dalam tes?
Bagian 1. Menguji pawang
Di luar kotak ada dukungan untuk server http di "net / http", sehingga Anda dapat mengangkatnya tanpa usaha. Peluang yang terbuka memungkinkan kita untuk merasa sangat kuat, dan karena itu kode kita akan mengembalikan pengguna ke-42.
func userHandler(w http.ResponseWriter, r *http.Request) { var user User userId, err := strconv.Atoi(r.URL.Query().Get("id")) if err != nil { w.Write([]byte( "Error")) return } if userId == 42 { user = User{userId, "Jack", 2} } jsonData, _ := json.Marshal(user) w.Write(jsonData) } type User struct { Id int Name string Rating uint }
Kode ini menerima parameter id pengguna sebagai input, kemudian mengemulasi keberadaan pengguna dalam database, dan kembali. Sekarang kita perlu mengujinya ...
Ada hal yang luar biasa โnet / http / httptestโ, memungkinkan Anda untuk mensimulasikan panggilan ke handler'a kami dan kemudian membandingkan jawabannya.
r := httptest.NewRequest("GET", "http://127.0.0.1:80/user?id=42", nil) w := httptest.NewRecorder() userHandler(w, r) user := User{} json.Unmarshal(w.Body.Bytes(), &user) if user.Id != 42 { t.Errorf("Invalid user id %d expected %d", user.Id, 42) }
Bagian 2. Sayang, kami memiliki API eksternal di sini
Dan mengapa kita perlu mengambil napas, jika kita baru saja melakukan pemanasan? Di dalam layanan kami, cepat atau lambat, api eksternal akan muncul. Ini adalah binatang aneh yang sering bersembunyi yang dapat berperilaku seperti yang diinginkan. Untuk tes, kami ingin kolega yang lebih akomodatif. Dan httptest kami yang baru ditemukan juga akan membantu kami di sini. Sebagai contoh, kode panggilan adalah api eksternal dengan transfer data lebih lanjut.
func ApiCaller(user *User, url string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() return updateUser(user, resp.Body) }
Untuk mengalahkan ini, kita dapat membuat tiruan API eksternal, opsi paling sederhana adalah:
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Access-Control-Allow-Origin", "*") fmt.Fprintln(w, `{ "result": "ok", "data": { "user_id": 1, "rating": 42 } }`) })) defer ts.Close() user := User{id: 1} err := ApiCaller(&user, ts.URL)
ts.URL akan berisi string format `http: //127.0.0.1: 49799`, yang akan menjadi api tiruan yang menyebut implementasi kami
Bagian 3. Mari kita bekerja dengan pangkalan
Ada cara sederhana: untuk menaikkan buruh pelabuhan dengan pangkalan, roll migrasi, perlengkapan dan menjalankan layanan terbaik kami. Tetapi mari kita coba menulis tes dengan ketergantungan minimum dengan layanan eksternal.
Implementasi bekerja dengan base in go memungkinkan Anda untuk mengganti driver itu sendiri, dan, melewati 100 halaman kode dan refleksi, saya sarankan Anda mengambil perpustakaan
github.com/DATA-DOG/go-sqlmockAnda dapat menangani sql.Db di dok. Mari kita ambil contoh yang sedikit lebih menarik, di mana akan ada orm for -
gorm .
func DbListener(db *gorm.DB) { user := User{} transaction := db.Begin() transaction.First(&user, 1) transaction.Model(&user).Update("counter", user.Counter+1) transaction.Commit() }
Saya harap contoh ini setidaknya membuat Anda berpikir bagaimana cara mengujinya. Dalam "mock.ExpectExec" Anda bisa mengganti ekspresi reguler yang mencakup case yang Anda butuhkan. Satu-satunya hal yang perlu diingat adalah bahwa urutan penetapan harapan harus sesuai dengan urutan dan jumlah panggilan.
func TestDbListener(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() mock.ExpectBegin() result := []string{"id", "name", "counter"} mock.ExpectQuery("SELECT \\* FROM `Users`").WillReturnRows(sqlmock.NewRows(result).AddRow(1, "Jack", 2)) mock.ExpectExec("UPDATE `Users`").WithArgs(3, 1).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() gormDB, _ := gorm.Open("mysql", db) DbListener(gormDB.LogMode(true)) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } }
Saya menemukan banyak contoh untuk menguji pangkalan di
sini .
Bagian 4. Bekerja dengan sistem file
Kami mencoba tangan kami di daerah yang berbeda dan berdamai bahwa semuanya baik untuk basah. Semuanya tidak begitu jelas di sini. Saya menyarankan dua pendekatan, basah atau gunakan sistem file.
Opsi 1 - kita semua basah di
github.com/spf13/aferoPro :
- Anda tidak perlu mengulang apa pun jika Anda sudah menggunakan perpustakaan ini. (tapi kemudian kamu bosan membaca itu)
- Bekerja dengan sistem file virtual, yang akan sangat mempercepat tes Anda.
Cons :
- Diperlukan modifikasi kode yang ada.
- Chmod tidak berfungsi pada sistem file virtual. Tapi itu bisa menjadi fitur sejak itu dokumentasi menyatakan "Hindari masalah keamanan dan izin".
Dari beberapa poin ini, saya langsung melakukan 2 tes. Dalam versi dengan sistem file, saya membuat file yang tidak dapat dibaca dan memeriksa cara kerja sistem.
func FileRead(path string) error { path = strings.TrimRight(path, "/") + "/" // files, err := ioutil.ReadDir(path) if err != nil { return fmt.Errorf("cannot read from file, %v", err) } for _, f := range files { deleteFileName := path + f.Name() _, err := ioutil.ReadFile(deleteFileName) if err != nil { return err } err = os.Remove(deleteFileName) // } return nil }
Menggunakan afero.Fs membutuhkan modifikasi minimal, tetapi pada dasarnya tidak ada perubahan dalam kode
func FileReadAlt(path string, fs afero.Fs) error { path = strings.TrimRight(path, "/") + "/" // files, err := afero.ReadDir(fs, path) if err != nil { return fmt.Errorf("cannot read from file, %v", err) } for _, f := range files { deleteFileName := path + f.Name() _, err := afero.ReadFile(fs, deleteFileName) if err != nil { return err } err = fs.Remove(deleteFileName) // } return nil }
Tapi kesenangan kita tidak akan lengkap kecuali kita mengetahui seberapa cepat afero daripada asli.
Menit benchmark:
BenchmarkIoutil 5000 242504 ns/op 7548 B/op 27 allocs/op BenchmarkAferoOs 300000 4259 ns/op 2144 B/op 30 allocs/op BenchmarkAferoMem 300000 4169 ns/op 2144 B/op 30 allocs/op
Jadi, pustaka adalah urutan besarnya di atas standar, tetapi menggunakan sistem file virtual atau yang asli adalah atas kebijakan Anda.
Saya merekomendasikan:
haisum.imtqy.com/2017/09/11/golang-ioutil-readallmatthias-endler.de/2018/go-io-testingKata penutup
Jujur saya sangat suka cakupan 100%, tetapi kode non-perpustakaan tidak membutuhkannya. Dan bahkan itu tidak menjamin perlindungan terhadap kesalahan. Fokus pada persyaratan bisnis, bukan kemampuan fungsi untuk mengembalikan 10 kesalahan yang berbeda.
Bagi mereka yang suka menyodok kode dan menjalankan tes,
repositori .