Gonkey - Alat Penguji Microservice

Gonkey sedang menguji layanan microser kami di Lamoda, dan kami pikir itu bisa menguji milik Anda, jadi kami menaruhnya di open source . Jika fungsionalitas layanan Anda diimplementasikan terutama melalui API, dan JSON digunakan untuk pertukaran data, maka Gonkey hampir pasti cocok untuk Anda.


gambar


Di bawah ini saya akan membicarakannya secara lebih rinci dan menunjukkan dengan contoh-contoh spesifik bagaimana menggunakannya.


Bagaimana Gonkey dilahirkan


Kami memiliki lebih dari seratus layanan mikro, yang masing-masing menyelesaikan tugas tertentu. Semua layanan memiliki API. Tentu saja, beberapa dari mereka juga merupakan antarmuka pengguna, tetapi, bagaimanapun, peran utama mereka adalah menjadi sumber data untuk situs, aplikasi seluler atau layanan internal lainnya, dan oleh karena itu menyediakan antarmuka perangkat lunak .


Ketika kami menyadari bahwa ada banyak layanan, dan kemudian akan ada lebih banyak lagi, kami mengembangkan dokumen internal yang menggambarkan pendekatan standar untuk desain API dan menggunakan Swagger sebagai alat bantu deskripsi (dan bahkan menulis utilitas untuk membuat kode berdasarkan spesifikasi swagger). Jika Anda tertarik mempelajari lebih lanjut tentang ini, lihat laporan Andrey dengan Highload ++.


Pendekatan standar untuk desain API secara alami mengarah pada gagasan pendekatan standar untuk pengujian. Inilah yang ingin saya capai:


  1. Tes layanan melalui API , karena hampir semua fungsi layanan diimplementasikan melalui API
  2. Kemampuan untuk mengotomatiskan peluncuran tes untuk mengintegrasikannya ke dalam proses CI / CD kami, seperti yang mereka katakan, "dijalankan oleh tombol"
  3. Tes menulis harus dapat diasingkan , yaitu, sehingga tidak hanya seorang programmer dapat menulis tes, idealnya seseorang yang tidak terbiasa dengan pemrograman.

Jadi Gonkey lahir.


Jadi apa ini?


Gonkey adalah perpustakaan (untuk proyek-proyek di Golang) dan utilitas konsol (untuk proyek dalam bahasa dan teknologi apa pun), yang dengannya Anda dapat melakukan pengujian fungsional dan regresi layanan dengan mengakses API mereka sesuai dengan skrip yang telah ditentukan. Script pengujian dijelaskan dalam file YAML.


Sederhananya, Gonkey dapat:


  • bombardir layanan Anda dengan permintaan HTTP dan pastikan responsnya seperti yang diharapkan. Ini mengasumsikan bahwa JSON digunakan dalam permintaan dan tanggapan, tetapi, kemungkinan besar, itu akan bekerja pada kasus-kasus sederhana dengan jawaban dalam format yang berbeda;
  • menyiapkan database untuk pengujian dengan mengisinya dengan data dari perlengkapan (juga ditentukan dalam file YAML);
  • meniru respons layanan eksternal menggunakan tiruan (fitur ini hanya tersedia jika Anda menghubungkan Gonkey sebagai perpustakaan);
  • memberikan hasil pengujian ke konsol atau menghasilkan laporan Allure.

Repositori proyek
Gambar buruh pelabuhan


Contoh Pengujian Layanan dengan Gonkey


Agar tidak membebani Anda dengan teks, saya ingin beralih dari kata-kata ke tindakan dan di sini menguji beberapa API dan memberi tahu dan menunjukkan bagaimana skrip tes ditulis sepanjang jalan.


Mari kita membuat sketsa layanan kecil di Go yang akan mensimulasikan pekerjaan lampu lalu lintas. Ini menyimpan warna sinyal saat ini: merah, kuning atau hijau. Anda bisa mendapatkan warna sinyal saat ini atau mengatur yang baru melalui API.


//    const ( lightRed = "red" lightYellow = "yellow" lightGreen = "green" ) //      type trafficLights struct { currentLight string `json:"currentLight"` mutex sync.RWMutex `json:"-"` } //   var lights = trafficLights{ currentLight: lightRed, } func main() { //       http.HandleFunc("/light/get", func(w http.ResponseWriter, r *http.Request) { lights.mutex.RLock() defer lights.mutex.RUnlock() resp, err := json.Marshal(lights) if err != nil { log.Fatal(err) } w.Write(resp) }) //       http.HandleFunc("/light/set", func(w http.ResponseWriter, r *http.Request) { lights.mutex.Lock() defer lights.mutex.Unlock() request, err := ioutil.ReadAll(r.Body) if err != nil { log.Fatal(err) } var newTrafficLights trafficLights if err := json.Unmarshal(request, &newTrafficLights); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := validateRequest(&newTrafficLights); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } lights = newTrafficLights }) //   () log.Fatal(http.ListenAndServe(":8080", nil)) } func validateRequest(lights *trafficLights) error { if lights.currentLight != lightRed && lights.currentLight != lightYellow && lights.currentLight != lightGreen { return fmt.Errorf("incorrect current light: %s", lights.currentLight) } return nil } 

Kode sumber lengkap untuk main.go ada di sini .


Jalankan program:


 go run . 

Sketsa sangat cepat dalam 15 menit! Tentunya dia salah di suatu tempat, jadi kami akan menulis tes dan memeriksa.


Unduh dan jalankan Gonkey:


 mkdir -p tests/cases docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080 

Perintah ini memulai gambar dengan gonkey melalui buruh pelabuhan, memasang direktori tes / kasus di dalam wadah dan memulai gonkey dengan parameter -tests test / cases / -host.


Jika Anda tidak menyukai pendekatan buruh pelabuhan, maka alternatif untuk perintah semacam itu adalah menulis:


 go get github.com/lamoda/gonkey go run github.com/lamoda/gonkey -tests tests/cases -host localhost:8080 

Diluncurkan dan dapatkan hasilnya:


 Failed tests: 0/0 

Tidak ada tes - tidak ada yang perlu diperiksa. Mari kita tulis tes pertama. Buat file test / cases / light_get.yaml dengan konten minimum:


 - name: WHEN currentLight is requested MUST return red method: GET path: /light/get response: 200: > { "currentLight": "red" } 

Pada level pertama adalah daftar. Ini berarti bahwa kami telah mendeskripsikan satu test case, tetapi mungkin ada banyak dari mereka dalam file. Bersama-sama mereka membuat skenario pengujian. Jadi, satu file - satu skrip. Anda dapat membuat sejumlah file dengan skrip uji, jika nyaman, mengaturnya menjadi subdirektori - gonkey membaca semua file yaml dan yml dari direktori yang ditransfer dan lebih rekursif.


File di bawah ini menjelaskan detail permintaan yang akan dikirim ke server: metode, jalur. Yang lebih rendah lagi adalah kode respons (200) dan badan respons yang kami harapkan dari server.


Format file lengkap dijelaskan dalam README .


Jalankan lagi:


 docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080 

Hasil:


  Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ values do not match: expected: { "currentLight": "red" } actual: {} Failed tests: 1/1 

Kesalahan! Struktur dengan bidangLightLight diharapkan, dan struktur kosong kembali. Ini buruk. Masalah pertama adalah bahwa hasilnya ditafsirkan sebagai string, ini ditunjukkan oleh fakta bahwa, sebagai tempat masalah, gonkey menyoroti seluruh jawaban tanpa perincian:


  expected: { "currentLight": "red" } 

Alasannya sederhana: Saya lupa menulis sehingga layanan dalam respons menunjukkan jenis konten aplikasi / json. Kami memperbaiki:


 //       http.HandleFunc("/light/get", func(w http.ResponseWriter, r *http.Request) { lights.mutex.RLock() defer lights.mutex.RUnlock() resp, err := json.Marshal(lights) if err != nil { log.Fatal(err) } w.Header().Add("Content-Type", "application/json") // <--  w.Write(resp) }) 

Kami memulai kembali layanan dan menjalankan tes lagi:


  Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ key is missing: expected: currentLight actual: <missing> 

Hebat, ada kemajuan. Gonkey sekarang mengenali strukturnya, tetapi masih salah: jawabannya kosong. Alasannya adalah bahwa saya menggunakan bidang currentLight yang tidak dapat diekspor dalam definisi tipe:


 //      type trafficLights struct { currentLight string `json:"currentLight"` mutex sync.RWMutex `json:"-"` } 

Di Go, bidang struktur bernama dengan huruf kecil dianggap tidak dapat diekspor, yaitu, tidak dapat diakses dari paket lain. Serializer JSON tidak melihatnya dan tidak dapat memasukkannya dalam respons. Kami benar: kami membuat bidang dengan huruf kapital, yang artinya diekspor:


 //      type trafficLights struct { urrentLight string `json:"currentLight"` // <--   mutex sync.RWMutex `json:"-"` } 

Mulai ulang layanan. Jalankan tes lagi.


 Failed tests: 0/1 

Tes telah berlalu!


Kami akan menulis skrip lain yang akan menguji metode yang ditetapkan. Isi file test / cases / light_set.yaml dengan konten berikut:


 - name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN get is requested MUST return green method: GET path: /light/get response: 200: > { "currentLight": "green" } 

Tes pertama menetapkan nilai baru untuk sinyal lalu lintas, dan yang kedua memeriksa status untuk memastikan bahwa itu telah berubah.


Jalankan tes dengan perintah yang sama:


 docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080 

Hasil:


 Failed tests: 0/3 

Hasil yang sukses, tetapi kami beruntung skrip dieksekusi dalam urutan yang kami butuhkan: light_get pertama, dan kemudian light_set. Apa yang akan terjadi jika mereka melakukan yang sebaliknya? Mari kita ganti nama:


 mv tests/cases/light_set.yaml tests/cases/_light_set.yaml 

Dan jalankan lagi:


 Errors: 1) at path $.currentLight values do not match: expected: red actual: green Failed tests: 1/3 

Pertama, set dieksekusi dan lampu lalu lintas dibiarkan dalam kondisi hijau, sehingga uji coba selanjutnya menemukan kesalahan - dia sedang menunggu merah.


Salah satu cara untuk menghilangkan fakta bahwa tes tergantung pada konteksnya adalah dengan menginisialisasi layanan di awal skrip (yaitu, pada awal file), yang biasanya kita lakukan dalam set test - pertama kita menetapkan nilai yang diketahui, yang seharusnya menghasilkan efek yang diketahui, dan kemudian periksa apakah efeknya berpengaruh.


Cara lain untuk menyiapkan konteks eksekusi jika layanan menggunakan database adalah dengan menggunakan perlengkapan dengan data yang dimuat ke dalam database di awal skrip, sehingga membentuk kondisi layanan yang dapat diprediksi yang dapat diperiksa. Deskripsi dan contoh bekerja dengan perlengkapan di gonkey saya ingin mengeluarkan dalam artikel terpisah.


Sementara itu, saya mengusulkan solusi berikut. Karena dalam skrip set, kami sebenarnya menguji metode light / set dan metode light / get, kami tidak memerlukan skrip light_get, yang peka konteks. Saya menghapusnya, dan mengganti nama skrip yang tersisa sehingga namanya mencerminkan esensi.


 rm tests/cases/light_get.yaml mv tests/cases/_light_set.yaml tests/cases/light_set_get.yaml 

Sebagai langkah selanjutnya, saya ingin memeriksa beberapa skenario negatif bekerja dengan layanan kami, misalnya, apakah itu akan berfungsi dengan benar jika saya mengirim warna sinyal yang salah? Atau tidak mengirim warna sama sekali?


Buat skrip test / cases / light_set_get_negative.yaml baru:


 - name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN incorrect color is passed MUST return error method: POST path: /light/set request: > { "currentLight": "blue" } response: 400: > incorrect current light: blue - name: WHEN color is missing MUST return error method: POST path: /light/set request: > {} response: 400: > incorrect current light: - name: WHEN get is requested MUST have color untouched method: GET path: /light/get response: 200: > { "currentLight": "green" } 

Dia memeriksa bahwa:


  • ketika warna yang salah ditransmisikan, kesalahan terjadi;
  • ketika warna tidak ditransmisikan, terjadi kesalahan;
  • transmisi warna yang salah tidak mengubah kondisi internal lampu lalu lintas.

Jalankan:


 Failed tests: 0/6 

Semuanya baik-baik saja :)


Menghubungkan Gonkey sebagai perpustakaan


Seperti yang Anda perhatikan, kami sedang menguji API layanan, sepenuhnya abstrak dari bahasa dan teknologi yang digunakan untuk menuliskannya. Dengan cara yang sama, kami dapat menguji API publik apa pun yang kami tidak memiliki akses ke kode sumber - itu cukup untuk mengirim permintaan dan menerima jawaban.


Tetapi untuk aplikasi kita sendiri yang ditulis in go, ada cara yang lebih mudah untuk menjalankan gonkey - untuk menghubungkannya ke proyek sebagai perpustakaan. Ini akan memungkinkan, tanpa mengkompilasi apa pun di muka - baik gonkey, maupun proyek itu sendiri - untuk menjalankan tes dengan hanya menjalankan go test .


Dengan pendekatan ini, kita sepertinya mulai menulis unit test, dan di dalam body test kita melakukan hal berikut:


  • menginisialisasi server web dengan cara yang sama seperti ketika layanan dimulai;
  • jalankan server aplikasi pengujian pada localhost dan port acak;
  • kami memanggil fungsi dari pustaka gonkey, mengirimkannya ke alamat server pengujian dan parameter lainnya. Di bawah ini saya akan menggambarkan ini.

Untuk melakukan ini, aplikasi kita perlu sedikit refactoring. Poin kuncinya adalah membuat penciptaan server fungsi yang terpisah, karena kita sekarang membutuhkan fungsi ini di dua tempat: ketika layanan dimulai, dan bahkan ketika tes gonkey dijalankan.


Saya meletakkan kode berikut dalam fungsi terpisah:


 func initServer() { //       http.HandleFunc("/light/get", func(w http.ResponseWriter, r *http.Request) { //   }) //       http.HandleFunc("/light/set", func(w http.ResponseWriter, r *http.Request) { //   }) } 

Fungsi utama akan seperti ini:


 func main() { initServer() //   () log.Fatal(http.ListenAndServe(":8080", nil)) } 

File go utama yang dimodifikasi sepenuhnya .


Ini membebaskan tangan kami, jadi mari kita mulai menulis ujian. Saya membuat file func_test.go:


 func Test_API(t *testing.T) { initServer() srv := httptest.NewServer(nil) runner.RunWithTesting(t, &runner.RunWithTestingParams{ Server: srv, TestsDir: "tests/cases", }) } 

Ini adalah file func_test.go lengkap .


Itu saja! Kami memeriksa:


 go test ./... 

Hasil:


 ok github.com/lamoda/gonkey/examples/traffic-lights-demo 0.018s 

Tes telah berlalu. Jika saya memiliki kedua tes unit dan tes gonkey, semuanya akan berjalan bersama - cukup nyaman.


Buat laporan Allure


Allure adalah format laporan pengujian untuk menampilkan hasil dengan cara yang jelas dan indah. Gonkey dapat merekam hasil tes dalam format ini. Mengaktifkan Allure sangat sederhana:


 docker run -it -v $(pwd)/tests:/tests -w /tests lamoda/gonkey -tests cases/ -host host.docker.internal:8080 -allure 

Laporan tersebut akan ditempatkan di subdirektori allure-results dari direktori kerja saat ini (itu sebabnya saya menentukan -w / tes).


Saat menghubungkan gonkey sebagai perpustakaan, laporan Allure diaktifkan dengan menetapkan variabel lingkungan tambahan GONKEY_ALLURE_DIR:


 GONKEY_ALLURE_DIR="tests/allure-results" go test ./… 

Hasil tes yang direkam dalam file dikonversi menjadi laporan interaktif dengan perintah:


 allure generate allure serve 

Seperti apa laporannya:
gambar


Kesimpulan


Dalam artikel berikut, saya akan membahas tentang penggunaan fixture di gonkey dan meniru tanggapan layanan lain menggunakan ejekan.


Saya mengundang Anda untuk mencoba gonkey di proyek Anda, berpartisipasi dalam pengembangannya (permintaan kumpulan dipersilahkan!) Atau tandai dengan tanda bintang di github jika proyek ini dapat berguna bagi Anda di masa depan.

Source: https://habr.com/ru/post/id463301/


All Articles