REST Assured - DSL untuk menguji layanan REST, yang tertanam dalam pengujian Java. Solusi ini muncul lebih dari sembilan tahun yang lalu dan telah menjadi populer karena kesederhanaan dan fungsionalitasnya yang nyaman.
Dalam DINS, kami menulis lebih dari 17.000 tes dengan itu dan selama lima tahun penggunaan, kami menemukan banyak "jebakan" yang tidak dapat ditemukan tepat setelah mengimpor perpustakaan ke dalam proyek: konteks statis, kebingungan dalam urutan filter yang diterapkan pada kueri, kesulitan dalam menyusun tes.
Artikel ini adalah tentang fitur implisit REST Tertanggung. Mereka perlu diperhitungkan jika ada kemungkinan bahwa jumlah tes dalam proyek akan meningkat dengan cepat - sehingga Anda tidak perlu menulis ulang nanti.

Apa yang kami uji
DINS terlibat dalam pengembangan platform UCaaS. Secara khusus, kami mengembangkan dan menguji API yang digunakan RingCentral sendiri dan menyediakan untuk pengembang pihak ketiga .
Saat mengembangkan API apa pun, penting untuk memastikan bahwa API itu berfungsi dengan benar, tetapi ketika Anda memberikannya, Anda harus memeriksa lebih banyak kasus. Oleh karena itu, puluhan dan ratusan tes ditambahkan ke setiap titik akhir baru. Tes ditulis dalam Java, TestNG dipilih sebagai kerangka uji, dan REST Assured digunakan untuk permintaan API.
Ketika REST Diasuransikan Akan Mendapat Manfaat
Jika tujuan Anda bukan untuk menguji keseluruhan API secara menyeluruh, maka cara termudah untuk melakukannya adalah dengan REST Assured. Sangat cocok untuk memeriksa struktur respons, PVD, dan tes asap.
Ini adalah bagaimana tes sederhana terlihat, yang akan memeriksa bahwa titik akhir memberikan status 200 OK ketika mengaksesnya:
given() .baseUri("http://cookiemonster.com") .when() .get("/cookies") .then() .assertThat() .statusCode(200);
Kata kunci yang given
, when
dan then
membentuk permintaan: given
menentukan apa yang akan dikirim dalam permintaan, when
–– dengan metode apa dan ke titik akhir mana kami mengirim permintaan, dan then
–– bagaimana respons yang diterima diperiksa. Selain itu, Anda bisa mengekstrak badan respons dalam bentuk objek bertipe JsonPath
atau XmlPath
, lalu menggunakan data yang diterima.
Tes nyata biasanya lebih besar dan lebih rumit. Tajuk, cookie, otorisasi, badan permintaan ditambahkan ke permintaan. Dan jika API yang diuji tidak terdiri dari lusinan sumber daya unik, yang masing-masing memerlukan parameter khusus, Anda akan ingin menyimpan template yang sudah jadi di suatu tempat untuk menambahkannya nanti ke panggilan tertentu dalam pengujian.
Untuk ini, di REST Tertanggung ada:
RequestSpecification
/ ResponseSpecification
;- konfigurasi dasar;
- filter.
RequestSpecification dan ResponseSpecification
Dua kelas ini memungkinkan Anda untuk menentukan parameter permintaan dan harapan dari respons:
RequestSpecification requestSpec = given() .baseUri("http://cookiemonster.com") .header("Language", "en"); requestSpec.when() .get("/cookiesformonster") .then() .statusCode(200); requestSpec.when() .get("/soup") .then() .statusCode(400);
ResponseSpecification responseSpec = expect() .statusCode(200); given() .expect() .spec(responseSpec) .when() .get("/hello"); given() .expect() .spec(responseSpec) .when() .get("/goodbye");
Satu spesifikasi digunakan dalam beberapa panggilan, tes, dan kelas uji, tergantung di mana ia didefinisikan - tidak ada batasan. Anda bahkan dapat menambahkan beberapa spesifikasi ke satu permintaan. Namun, ini merupakan sumber masalah potensial:
RequestSpecification requestSpec = given() .baseUri("http://cookiemonster.com") .header("Language", "en"); RequestSpecification yetAnotherRequestSpec = given() .header("Language", "fr"); given() .spec(requestSpec) .spec(yetAnotherRequestSpec) .when() .get("/cookies") .then() .statusCode(200);
Log Panggilan:
Request method: GET Request URI: http://localhost:8080/ Headers: Language=en Language=fr Accept=*/* Cookies: <none> Multiparts: <none> Body: <none> java.net.ConnectException: Connection refused (Connection refused)
Ternyata semua header ditambahkan ke panggilan, tetapi URI tiba-tiba menjadi hosting lokal - meskipun ditambahkan dalam spesifikasi pertama.
Ini terjadi karena fakta bahwa REST Assured menangani penggantian untuk parameter permintaan secara berbeda (sama dengan jawabannya). Header atau filter ditambahkan ke daftar dan kemudian diterapkan secara bergantian. Hanya ada satu URI, jadi yang terakhir diterapkan. Itu tidak ditentukan dalam spesifikasi terakhir yang ditambahkan - oleh karena itu, REST Assured menimpanya dengan nilai default (localhost).
Jika Anda menambahkan spesifikasi ke permintaan, tambahkan satu . Nasihatnya tampak jelas, tetapi ketika proyek dengan tes tumbuh, kelas pembantu dan kelas tes dasar muncul, sebelum metode muncul di dalamnya. Melacak apa yang sebenarnya terjadi dengan permintaan Anda menjadi sulit, terutama jika beberapa orang menulis tes sekaligus.
Konfigurasi REST Dasar Terjamin
Cara lain untuk templat kueri di REST Assured adalah mengonfigurasi konfigurasi dasar dan menentukan bidang statis kelas RestAssured:
@BeforeMethod public void configureRestAssured(...) { RestAssured.baseURI = "http://cookiemonster.com"; RestAssured.requestSpecification = given() .header("Language", "en"); RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); ... }
Nilai akan ditambahkan secara otomatis ke permintaan setiap kali. Konfigurasi ini dikombinasikan dengan penjelasan @BeforeMethod
di TestNG dan @BeforeEach
di JUnit - sehingga Anda dapat yakin bahwa setiap pengujian yang Anda jalankan akan mulai dengan parameter yang sama.
Namun, konfigurasi akan menjadi sumber masalah potensial, karena bersifat statis .
Contoh: sebelum setiap pengujian, kami mengambil pengguna tes, mendapatkan token otorisasi untuknya, dan kemudian menambahkannya melalui AuthenticationScheme atau filter otorisasi ke konfigurasi dasar. Selama tes berjalan dalam satu utas, semuanya akan bekerja.
Ketika ada terlalu banyak tes, keputusan biasa untuk membagi eksekusi menjadi beberapa utas akan menyebabkan penulisan ulang sepotong kode sehingga token dari satu utas tidak jatuh ke yang tetangga.
REST Filter Tertanggung
Filter memodifikasi kedua permintaan sebelum mengirim dan merespons sebelum memeriksa kepatuhan dengan harapan yang ditentukan. Contoh aplikasi - menambahkan logging, atau otorisasi:
public class OAuth2Filter implements AuthFilter { String accessToken; OAuth2Filter(String accessToken) { this.accessToken = accessToken; } @Override public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) { requestSpec.replaceHeader("Authorization", "Bearer " + accessToken); return ctx.next(requestSpec, responseSpec); } }
String accessToken = getAccessToken(username, password); OAuth2Filter auth = new OAuth2Filter(accessToken); given() .filter(auth) .filter(new RequestLoggingFilter()) .filter(new ResponseLoggingFilter()) ...
Filter yang ditambahkan ke permintaan disimpan di LinkedList
. Sebelum mengajukan permintaan, REST Assured memodifikasinya dengan menelusuri daftar dan menerapkan satu filter setelah filter lainnya. Kemudian hal yang sama dilakukan dengan jawaban yang datang.
Urutan filter penting . Dua kueri ini akan menghasilkan log yang berbeda: yang pertama menunjukkan header otorisasi, yang kedua - tidak. Dalam hal ini, tajuk akan ditambahkan ke kedua permintaan - hanya dalam kasus pertama, REST Tertanggung pertama akan menambahkan otorisasi sebelum mendaftar, dan sebaliknya - sebaliknya.
given() .filter(auth) .filter(new RequestLoggingFilter()) … given() .filter(new RequestLoggingFilter()) .filter(auth)
Selain aturan umum bahwa filter diterapkan dalam urutan penambahannya, masih ada peluang untuk memprioritaskan filter Anda dengan mengimplementasikan antarmuka OrderedFilter
. Ini memungkinkan Anda untuk menetapkan prioritas numerik khusus untuk filter, di atas atau di bawah default (1000). Filter dengan prioritas di atas akan dieksekusi lebih awal dari biasanya, dengan prioritas di bawah - setelah mereka.
Tentu saja, di sini Anda dapat menjadi bingung dan secara tidak sengaja mengatur dua filter ke prioritas yang sama, misalnya, pada 999. Kemudian yang ditambahkan sebelumnya akan diterapkan pada permintaan terlebih dahulu.
Tidak hanya filter
Cara melakukan otorisasi melalui filter ditunjukkan di atas. Namun selain metode ini di REST Assured, ada metode lain, melalui AuthenticationScheme
:
String accessToken = getAccessToken(username, password); OAuth2Scheme scheme = new OAuth2Scheme(); scheme.setAccessToken(accessToken); RestAssured.authentication = scheme;
Ini adalah metode yang usang. Sebaliknya, Anda harus memilih yang ditunjukkan di atas. Ada dua alasan:
Masalah Ketergantungan
Dokumentasi untuk REST Tertanggung menunjukkan bahwa untuk menggunakan Oauth1 atau Oauth2 (dengan menetapkan token sebagai parameter kueri), otorisasi harus ditambahkan tergantung pada juru tulis. Namun, mengimpor versi terbaru tidak akan membantu Anda - Anda akan memiliki kesalahan yang dijelaskan di salah satu masalah terbuka . Anda dapat menyelesaikannya hanya dengan mengimpor perpustakaan versi lama, 2.5.3. Namun, dalam hal ini Anda akan menemukan masalah lain .
Secara umum, tidak ada versi lain dari Scribe yang berfungsi dengan Oauth2 REST Assured versi 3.0.3 dan lebih tinggi (dan rilis terbaru 4.0.0 tidak memperbaiki ini).
Pencatatan tidak berfungsi
Filter diterapkan ke kueri dalam urutan tertentu. Dan AuthenticationScheme
diterapkan setelah mereka. Ini berarti bahwa akan sulit untuk mendeteksi masalah dengan otorisasi dalam tes - tidak dijaminkan.
Lebih lanjut tentang sintaks REST Assured
Sejumlah besar tes biasanya berarti bahwa mereka juga kompleks. Dan jika API adalah subjek utama pengujian, dan Anda perlu memeriksa tidak hanya bidang json, tetapi logika bisnis, kemudian dengan REST Assured, pengujian berubah menjadi lembaran:
@Test public void shouldCorrectlyCountAddedCookies() { Integer addNumber = 10; JsonPath beforeCookies = given() .when() .get("/latestcookies") .then() .assertThat() .statusCode(200) .extract() .jsonPath(); String beforeId = beforeCookies.getString("id"); JsonPath afterCookies = given() .body(String.format("{number: %s}", addNumber)) .when() .put("/cookies") .then() .assertThat() .statusCode(200) .extract() .jsonPath(); Integer afterNumber = afterCookies.getInt("number"); String afterId = afterCookies.getString("id"); JsonPath history = given() .when() .get("/history") .then() .assertThat() .statusCode(200) .extract() .jsonPath(); assertThat(history.getInt(String.format("records.find{r -> r.id == %s}.number", beforeId))) .isEqualTo(afterNumber - addNumber); assertThat(history.getInt(String.format("records.find{r -> r.id == %s}.number", afterId))) .isEqualTo(afterNumber); }
Tes ini memverifikasi bahwa ketika kami memberi makan cookie monster, kami menghitung dengan benar berapa banyak cookie yang diberikan kepadanya, dan menunjukkan ini dalam cerita. Tetapi pada pandangan pertama ini tidak dapat dipahami - semua permintaan terlihat sama, dan tidak jelas di mana persiapan data melalui API berakhir, dan di mana permintaan pengujian dikirim.
given()
, when()
dan then()
REST Assured mengambil dari BDD, seperti Spock atau Mentimun. Namun, dalam tes yang kompleks, maknanya hilang, karena skala tes menjadi jauh lebih besar dari satu permintaan - ini adalah satu tindakan kecil yang perlu dilambangkan dengan satu baris. Dan untuk ini, Anda dapat mentransfer panggilan REST Assured ke kelas tambahan:
public class CookieMonsterHelper { public static JsonPath getCookies() { return given() .when() .get("/cookiesformonster") .then() .extract() .jsonPath(); } ... }
Dan hubungi tes:
JsonPath response = CookieMonsterHelper.getCookies();
Ada baiknya ketika kelas pembantu seperti itu bersifat universal sehingga panggilan ke satu metode dapat tertanam dalam sejumlah besar tes - maka mereka dapat dimasukkan ke perpustakaan terpisah secara umum: tiba-tiba, Anda perlu memanggil metode di beberapa titik di proyek lain. Hanya dalam kasus ini Anda harus menghapus semua verifikasi dari respons yang dapat dilakukan oleh Tertanggung - lagi pula, data yang sangat berbeda seringkali dapat dikembalikan sebagai tanggapan atas permintaan yang sama.
Kesimpulan
REST Assured adalah perpustakaan untuk pengujian. Dia tahu bagaimana melakukan dua hal: mengirim permintaan dan memeriksa jawaban. Jika kami mencoba menghapusnya dari pengujian dan menghapus semua validasi, maka itu berubah menjadi klien HTTP .
Jika Anda harus menulis sejumlah besar tes dan terus mendukungnya, pikirkan apakah Anda memerlukan klien HTTP dengan sintaks yang rumit, konfigurasi statis, kebingungan dalam urutan penerapan filter dan spesifikasi, dan pencatatan yang mudah rusak? Mungkin sembilan tahun yang lalu, REST Assured adalah alat yang paling nyaman, tetapi selama ini alternatif muncul - Retrofit, Feign, Unirest, dll - yang tidak memiliki fitur seperti itu.
Sebagian besar masalah yang dijelaskan dalam artikel memanifestasikan diri dalam proyek-proyek besar. Jika Anda perlu dengan cepat menulis beberapa tes dan melupakannya selamanya, dan Retrofit tidak menyukainya, REST Assured adalah pilihan terbaik.
Jika Anda sudah menulis tes menggunakan REST Assured, tidak perlu terburu-buru untuk menulis ulang semuanya. Jika mereka stabil dan cepat, itu akan menghabiskan lebih banyak waktu daripada membawa manfaat praktis. Jika tidak, REST Tertanggung bukan masalah utama Anda.
Setiap hari, jumlah tes yang ditulis dalam DINS untuk RingCentral API terus bertambah, dan mereka masih menggunakan REST Assured. Jumlah waktu yang harus dihabiskan untuk beralih ke klien HTTP lain, setidaknya dalam pengujian baru, terlalu besar, dan kelas pembantu yang dibuat dan metode yang mengonfigurasi konfigurasi pengujian menyelesaikan sebagian besar masalah. Dalam hal ini, menjaga integritas proyek dengan tes lebih penting daripada menggunakan klien yang paling cantik dan modis. REST Diasuransikan, terlepas dari kekurangannya, melakukan tugas utamanya.