Metode Pengujian `test::nginx`: Konfigurasi, Mengirim Permintaan, dan Menangani Respons
API7.ai
November 18, 2022
Pada artikel terakhir, kita sudah mendapatkan gambaran pertama tentang test::nginx dan menjalankan contoh paling sederhana. Namun, dalam proyek open-source nyata, kasus uji yang ditulis dalam test::nginx jauh lebih kompleks dan sulit untuk dikuasai dibandingkan dengan kode contoh. Jika tidak, itu tidak akan disebut sebagai hambatan.
Dalam artikel ini, saya akan membawa Anda melalui perintah dan metode pengujian yang sering digunakan dalam test::nginx, sehingga Anda dapat memahami sebagian besar set kasus uji dalam proyek OpenResty dan memiliki kemampuan untuk menulis kasus uji yang lebih realistis. Bahkan jika Anda belum berkontribusi kode ke OpenResty, mengenal kerangka pengujian OpenResty akan menjadi inspirasi besar bagi Anda untuk merancang dan menulis kasus uji dalam pekerjaan Anda.
Pengujian test::nginx pada dasarnya menghasilkan nginx.conf dan memulai proses NGINX berdasarkan konfigurasi setiap kasus uji. Kemudian, itu mensimulasikan permintaan klien dengan badan permintaan dan header yang ditentukan. Selanjutnya, kode Lua dalam kasus uji memproses permintaan dan memberikan respons. Pada saat ini, test::nginx memparsing informasi kritis seperti badan respons, header respons, dan log kesalahan dan membandingkannya dengan konfigurasi pengujian. Jika ada perbedaan, pengujian gagal dengan kesalahan; jika tidak, pengujian berhasil.
test::nginx menyediakan banyak primitif DSL (Domain-specific language). Saya telah membuat klasifikasi sederhana sesuai dengan konfigurasi NGINX, mengirim permintaan, memproses respons, dan memeriksa log. 20% dari fungsionalitas ini dapat mencakup 80% dari skenario aplikasi, jadi kita harus memahaminya dengan baik. Adapun primitif dan penggunaan yang lebih lanjut, kita akan membahasnya di artikel berikutnya.
Konfigurasi NGINX
Mari kita lihat terlebih dahulu konfigurasi NGINX. Primitif test::nginx dengan kata kunci "config" terkait dengan konfigurasi NGINX, seperti config, stream_config, http_config, dll.
Fungsinya sama: menyisipkan konfigurasi NGINX yang ditentukan ke dalam konteks NGINX yang berbeda. Konfigurasi ini dapat berupa perintah NGINX atau kode Lua yang dibungkus dalam content_by_lua_block.
Saat melakukan pengujian unit, config adalah primitif yang paling sering digunakan di mana kita memuat pustaka Lua dan memanggil fungsi untuk pengujian white-box. Berikut adalah cuplikan kode pengujian, yang tidak dapat dijalankan sepenuhnya. Ini berasal dari proyek open-source nyata, jadi jika Anda tertarik, Anda dapat mengklik tautan untuk melihat pengujian lengkap, atau Anda dapat mencoba menjalankannya secara lokal.
=== TEST 1: sanity --- config location /t { content_by_lua_block { local plugin = require("apisix.plugins.key-auth") local ok, err = plugin.check_schema({key = 'test-key'}) if not ok then ngx.say(err) end ngx.say("done") } }
Tujuan dari kasus uji ini adalah untuk menguji apakah fungsi check_schema dalam file kode plugins.key-auth berfungsi dengan baik. Ini menggunakan perintah NGINX content_by_lua_block dalam location /t untuk memerlukan modul yang akan diuji dan memanggil fungsi yang perlu diperiksa secara langsung.
Ini adalah cara umum pengujian white-box dalam test::nginx. Namun, konfigurasi ini saja tidak cukup untuk menyelesaikan pengujian, jadi mari kita lanjutkan dan lihat bagaimana cara mengirim permintaan klien.
Mengirim Permintaan
Mensimulasikan klien yang mengirim permintaan melibatkan cukup banyak detail, jadi mari kita mulai dengan yang paling sederhana - mengirim satu permintaan.
request
Lanjutkan dengan kasus uji di atas, jika kita ingin kode pengujian unit dijalankan, maka kita harus memulai permintaan HTTP ke alamat /t yang ditentukan dalam konfigurasi, seperti yang ditunjukkan dalam kode pengujian berikut:
--- request GET /t
Kode ini mengirim permintaan GET ke /t dalam primitif permintaan. Di sini, kita tidak menentukan alamat IP, nama domain, atau port akses, juga tidak menentukan apakah itu HTTP 1.0 atau HTTP 1.1. Semua detail ini disembunyikan oleh test::nginx, jadi kita tidak perlu memikirkannya. Ini adalah salah satu manfaat DSL - kita hanya perlu fokus pada logika bisnis tanpa terganggu oleh semua detail.
Selain itu, ini memberikan fleksibilitas parsial. Misalnya, defaultnya adalah protokol untuk HTTP 1.1, atau jika kita ingin menguji HTTP 1.0, kita dapat menentukannya secara terpisah:
--- request GET /t HTTP/1.0
Selain metode GET, metode POST juga perlu didukung. Dalam contoh berikut, kita dapat POST string hello world ke alamat yang ditentukan.
--- request POST /t hello world
Sekali lagi, test::nginx menghitung panjang badan permintaan untuk Anda di sini, dan menambahkan header permintaan host dan connection untuk memastikan bahwa ini adalah permintaan normal secara otomatis.
Tentu saja, kita dapat menambahkan komentar untuk membuatnya lebih mudah dibaca. Yang dimulai dengan # akan dikenali sebagai komentar kode.
--- request # post request POST /t hello world
Permintaan juga mendukung mode yang lebih kompleks dan fleksibel, yang menggunakan eval sebagai filter untuk menyematkan kode Perl secara langsung karena test::nginx ditulis dalam Perl. Jika bahasa DSL saat ini tidak memenuhi kebutuhan Anda, eval adalah "senjata pamungkas" untuk menjalankan kode Perl secara langsung.
Untuk penggunaan eval, mari kita lihat beberapa contoh sederhana di sini, dan kita akan melanjutkan dengan yang lebih kompleks di artikel berikutnya.
--- request eval "POST /t hello\x00\x01\x02 world\x03\x04\xff"
Dalam contoh pertama, kita menggunakan eval untuk menentukan karakter yang tidak dapat dicetak, yang merupakan salah satu kegunaannya. Konten di antara tanda kutip ganda akan diperlakukan sebagai string Perl dan kemudian diteruskan ke request sebagai argumen.
Berikut adalah contoh yang lebih menarik:
--- request eval "POST /t\n" . "a" x 1024
Namun, untuk memahami contoh ini, kita perlu mengetahui sedikit tentang string dalam Perl, jadi saya perlu menyebutkan dua poin di sini.
- Dalam Perl, kita menggunakan titik untuk mewakili penggabungan string. Bukankah ini agak mirip dengan dua titik dalam
Lua? - Huruf kecil
xmenunjukkan berapa kali karakter diulang. Misalnya,"a" x 1024di atas berarti karakter "a" diulang 1024 kali.
Jadi, contoh kedua berarti metode POST mengirim permintaan yang berisi 1024 karakter a ke alamat /t.
pipelined_requests
Setelah memahami cara mengirim satu permintaan, mari kita lihat cara mengirim beberapa permintaan. Dalam test::nginx, kita dapat menggunakan primitif pipelined_requests untuk mengirim beberapa permintaan secara berurutan dalam koneksi keep-alive yang sama:
--- pipelined_requests eval ["GET /hello", "GET /world", "GET /foo", "GET /bar"]
Misalnya, contoh ini akan mengakses keempat API ini secara berurutan dalam koneksi yang sama. Ada dua keuntungan tentang ini:
- Yang pertama adalah banyak kode pengujian yang berulang dapat dihilangkan, dan empat kasus uji dapat dikompres menjadi satu.
- Yang kedua dan paling penting adalah kita dapat menggunakan permintaan pipelined untuk mendeteksi apakah logika kode akan memiliki pengecualian dalam kasus beberapa akses.
Anda mungkin bertanya-tanya, saya menulis beberapa kasus uji secara berurutan, maka kode juga akan dieksekusi beberapa kali dalam fase eksekusi. Bukankah itu juga mencakup masalah kedua di atas?
Ini berkaitan dengan mode eksekusi test::nginx, yang bekerja berbeda dari yang Anda pikirkan. Setelah setiap kasus uji, test::nginx mematikan proses NGINX saat ini, dan semua data dalam memori menghilang. Saat menjalankan kasus uji berikutnya, nginx.conf dihasilkan ulang, dan Worker NGINX baru dimulai. Mekanisme ini memastikan bahwa kasus uji tidak saling mempengaruhi.
Jadi, ketika kita ingin menguji beberapa permintaan, kita perlu menggunakan primitif pipelined_requests. Berdasarkan itu, kita dapat mensimulasikan pembatasan laju, pembatasan konkurensi, dan banyak skenario lain untuk menguji apakah sistem Anda bekerja dengan baik dengan skenario yang lebih realistis dan kompleks. Kita akan membahas ini di artikel berikutnya juga, karena ini akan melibatkan beberapa perintah dan primitif.
repeat_each
Kita baru saja menyebutkan kasus pengujian beberapa permintaan, jadi bagaimana seharusnya kita menjalankan pengujian yang sama beberapa kali?
Untuk masalah ini, test::nginx menyediakan pengaturan global: repeat_each, yang merupakan fungsi Perl yang defaultnya adalah repeat_each(1), menunjukkan bahwa kasus uji hanya akan dijalankan sekali. Jadi dalam kasus uji sebelumnya, kita tidak perlu repot mengaturnya secara terpisah.
Secara alami, kita dapat mengaturnya sebelum fungsi run_test(), misalnya, dengan mengubah argumen menjadi 2.
repeat_each(2); run_tests();
Kemudian, setiap kasus uji dijalankan dua kali, dan seterusnya.
more_headers
Setelah membahas badan permintaan, mari kita lihat header permintaan. Seperti yang kita sebutkan di atas, test::nginx mengirim permintaan dengan header host dan connection secara default. Bagaimana dengan header permintaan lainnya?
more_headers dirancang khusus untuk melakukan hal itu.
--- more_headers X-Foo: blah
Kita dapat menggunakannya untuk mengatur berbagai header kustom. Jika kita ingin mengatur lebih dari satu header, maka atur lebih dari satu baris:
--- more_headers X-Foo: 3 User-Agent: openresty
Memproses Respons
Setelah mengirim permintaan, bagian terpenting dari test::nginx adalah memproses respons, di mana kita akan menentukan apakah respons memenuhi harapan. Di sini kita membaginya menjadi empat bagian dan memperkenalkannya: badan respons, header respons, kode status respons, dan log.
response_body
Pasangan dari primitif permintaan adalah response_body, dan berikut adalah contoh dari dua konfigurasi mereka dalam penggunaan:
=== TEST 1: sanity --- config location /t { content_by_lua_block { ngx.say("hello") } } --- request GET /t --- response_body hello
Kasus uji ini akan berhasil jika badan respons adalah hello, dan akan melaporkan kesalahan dalam kasus lain. Tetapi bagaimana kita menguji badan respons yang panjang? Jangan khawatir, test::nginx sudah mengurusnya untuk Anda. Ini mendukung deteksi badan respons dengan ekspresi reguler, seperti berikut:
--- response_body_like ^he\w+$
Ini memungkinkan Anda untuk sangat fleksibel dengan badan respons. Selain itu, test::nginx juga mendukung operasi unlike:
--- response_body_unlike ^he\w+$
Pada titik ini, jika badan respons adalah hello, pengujian tidak akan berhasil.
Sejalan dengan itu, setelah memahami deteksi satu permintaan, mari kita lihat deteksi beberapa permintaan. Berikut adalah contoh cara menggunakannya dengan pipelined_requests:
--- pipelined_requests eval ["GET /hello", "GET /world", "GET /foo", "GET /bar"] --- response_body eval ["hello", "world", "oo", "bar"]
Tentu saja, yang penting untuk diperhatikan di sini adalah sebanyak permintaan yang Anda kirim, Anda perlu memiliki sebanyak respons yang sesuai.
response_headers
Kedua, mari kita bicara tentang header respons. Header respons mirip dengan header permintaan di mana setiap baris sesuai dengan kunci dan nilai dari sebuah header.
--- response_headers X-RateLimit-Limit: 2 X-RateLimit-Remaining: 1
Seperti deteksi badan respons, header respons juga mendukung ekspresi reguler dan operasi unlike, seperti response_headers_like, raw_response_headers_like, dan raw_response_headers_unlike.
error_code
Yang ketiga adalah kode respons. Deteksi kode respons mendukung perbandingan langsung dan juga mendukung operasi like, seperti dua contoh berikut:
--- error_code: 302
--- error_code_like: ^(?:500)?$
Dalam kasus beberapa permintaan, error_code perlu diperiksa beberapa kali:
--- pipelined_requests eval ["GET /hello", "GET /hello", "GET /hello", "GET /hello"] --- error_code eval [200, 200, 503, 503]
error_log
Item pengujian terakhir adalah log kesalahan. Dalam kebanyakan kasus uji, tidak ada log kesalahan yang dihasilkan. Kita dapat menggunakan no_error_log untuk mendeteksi:
--- no_error_log [error]
Dalam contoh di atas, jika string [error] muncul dalam error.log NGINX, pengujian akan gagal. Ini adalah fitur yang sangat umum, dan disarankan agar Anda menambahkan deteksi log kesalahan ke semua pengujian normal Anda.
--- error_log hello world
Konfigurasi di atas adalah mendeteksi keberadaan hello world dalam error.log. Tentu saja, Anda dapat menggunakan eval yang disematkan dalam kode Perl untuk mengimplementasikan deteksi ekspresi reguler, seperti berikut:
--- error_log eval qr/\[notice\] .*? \d+ hello world/
Ringkasan
Hari ini, kita belajar cara mengirim permintaan dan menguji respons dalam test::nginx, termasuk badan permintaan, header, kode status respons, dan log kesalahan. Kita dapat mengimplementasikan set kasus uji yang lengkap dengan kombinasi primitif ini.
Terakhir, berikut adalah pertanyaan pemikiran: Apa kelebihan dan kekurangan dari test::nginx, sebuah DSL abstrak? Jangan ragu untuk meninggalkan komentar dan berdiskusi dengan saya, dan Anda juga dipersilakan untuk membagikan artikel ini untuk berkomunikasi dan berpikir bersama.