Strategi yang efektif untuk pengujian kode otomatis sangat penting untuk memastikan kerja tim pemrogram yang cepat dan berkualitas tinggi yang terlibat dalam dukungan dan pengembangan proyek web. Penulis artikel itu mengatakan bahwa di perusahaan
StackPath , tempat dia bekerja, sekarang semuanya bekerja dengan
baik dengan pengujian. Mereka memiliki banyak alat untuk memeriksa kode. Tetapi dari varietas seperti itu Anda perlu memilih apa yang paling cocok untuk setiap kasus. Ini masalah tersendiri. Dan setelah alat yang diperlukan dipilih, Anda masih perlu membuat keputusan tentang urutan penggunaannya.

Penulis artikel tersebut mengatakan bahwa StackPath puas dengan tingkat kepercayaan terhadap kualitas kode yang dicapai berkat sistem pengujian yang diterapkan. Di sini dia ingin berbagi deskripsi tentang prinsip pengujian yang dikembangkan oleh perusahaan dan berbicara tentang alat yang digunakan.
Prinsip Pengujian
Sebelum berbicara tentang alat khusus, ada baiknya memikirkan jawaban untuk pertanyaan apa tes yang baik. Sebelum mulai bekerja di
portal kami untuk klien, kami merumuskan dan menuliskan prinsip-prinsip yang ingin kami ikuti saat membuat tes. Apa yang kami lakukan pada awalnya adalah apa yang membantu kami dengan pilihan alat.
Inilah empat prinsip yang dipertanyakan.
▍ Prinsip nomor 1. Tes harus dipahami sebagai tugas pengoptimalan
Strategi pengujian yang efektif adalah untuk memecahkan masalah memaksimalkan nilai tertentu (dalam hal ini, tingkat kepercayaan bahwa aplikasi akan bekerja dengan benar) dan meminimalkan biaya tertentu (di sini "biaya" diwakili oleh waktu yang diperlukan untuk mendukung dan menjalankan tes). Saat menulis tes, kami sering mengajukan pertanyaan berikut terkait dengan prinsip yang dijelaskan di atas:
- Apa kemungkinan tes ini akan menemukan kesalahan?
- Apakah pengujian ini meningkatkan sistem pengujian kami dan apakah biaya sumber daya yang dibutuhkan untuk menulis sepadan dengan manfaat yang diperoleh darinya?
- Apakah mungkin untuk mendapatkan tingkat kepercayaan yang sama pada entitas yang diuji yang diberikan tes ini dengan membuat tes lain yang lebih mudah untuk ditulis, dipelihara, dan dijalankan?
▍ Prinsip No. 2. Penggunaan mox secara berlebihan harus dihindari.
Salah satu penjelasan favorit saya dari istilah "mok" diberikan dalam presentasi
ini dari konferensi Assert.js 2018. Pembicara membuka pertanyaan lebih dalam daripada saya akan membukanya di sini. Dalam pidatonya, penciptaan mokas dibandingkan dengan “lubang berlubang dalam kenyataan”. Dan saya pikir ini adalah cara yang sangat visual untuk memahami moks. Meskipun ada mokas dalam tes kami, kami membandingkan penurunan “biaya” tes yang disediakan moka karena penyederhanaan proses penulisan dan tes berjalan, dengan penurunan nilai tes yang menyebabkan lubang lain dibuat pada kenyataannya.
Sebelumnya, programmer kami sangat bergantung pada unit test yang ditulis sehingga semua dependensi anak diganti dengan moka menggunakan API rendering
enzim dangkal. Entitas yang diberikan dengan cara ini kemudian diperiksa menggunakan snapshot
Jest . Semua tes semacam itu ditulis menggunakan pola yang serupa:
it('renders ', () => { const wrapper = shallow();
Tes-tes ini dipenuhi dengan kenyataan di banyak tempat. Pendekatan ini membuatnya sangat mudah untuk mencapai cakupan kode 100% dengan tes. Saat menulis tes semacam itu, Anda harus berpikir sangat sedikit, tetapi jika Anda tidak memeriksa semua poin integrasi, tes semacam itu tidak terlalu berharga. Semua tes berhasil diselesaikan, tetapi ini tidak memberikan banyak kepercayaan pada operabilitas aplikasi. Dan lebih buruk lagi, semua moka memiliki "harga" tersembunyi yang harus Anda bayar setelah tes ditulis.
▍ Prinsip No. 3. Tes harus memfasilitasi kode refactoring, bukan mempersulitnya.
Tes seperti yang ditunjukkan di atas mempersulit refactoring. Jika saya menemukan bahwa di banyak tempat dalam proyek terdapat kode duplikat, dan setelah beberapa saat saya memformat kode ini sebagai komponen terpisah, maka semua tes untuk komponen yang akan saya gunakan komponen baru ini akan gagal. Komponen yang diturunkan menggunakan teknik rendering dangkal sudah sesuatu yang lain. Di mana saya pernah melakukan markup berulang, sekarang ada komponen baru.
Refactoring yang lebih kompleks, yang melibatkan penambahan beberapa komponen ke proyek dan menghapus beberapa komponen lagi, menyebabkan kebingungan yang lebih besar. Faktanya adalah Anda harus menambahkan tes baru ke sistem dan menghapus tes yang tidak perlu dari itu. Regenerasi snapshot adalah tugas yang sederhana, tetapi apa nilai dari tes tersebut? Bahkan jika mereka dapat menemukan kesalahan, akan lebih baik jika mereka melewatkannya dalam serangkaian perubahan snapshot dan cukup memeriksa snapshot baru tanpa menghabiskan terlalu banyak waktu untuk itu.
Akibatnya, tes semacam itu tidak secara khusus membantu refactoring. Idealnya, tidak ada tes yang gagal jika saya melakukan refactoring, setelah itu apa yang dilihat pengguna dan apa yang berinteraksi dengannya tidak berubah. Dan sebaliknya - jika saya mengubah apa yang dihubungi pengguna, setidaknya satu tes harus gagal. Jika pengujian mengikuti dua aturan ini, maka itu adalah alat yang sangat baik untuk memastikan bahwa sesuatu yang ditemui pengguna tidak berubah selama refactoring.
▍ Prinsip No. 4. Tes harus mereproduksi cara pengguna yang sebenarnya bekerja dengan aplikasi
Saya ingin tes gagal hanya jika ada sesuatu yang berubah yang berinteraksi dengan pengguna. Ini berarti bahwa tes harus bekerja dengan aplikasi dengan cara yang sama seperti pengguna bekerja dengannya. Misalnya, tes harus benar-benar berinteraksi dengan elemen formulir dan, seperti halnya pengguna, harus memasukkan teks dalam bidang input teks. Pengujian tidak boleh mengakses komponen dan secara mandiri menyebut metode siklus hidupnya, tidak boleh menulis sesuatu ke dalam status komponen, atau melakukan sesuatu yang bergantung pada seluk-beluk implementasi komponen. Karena, pada akhirnya, saya ingin memeriksa bagian dari sistem yang bersentuhan dengan pengguna, masuk akal untuk berusaha memastikan bahwa pengujian ketika berinteraksi dengan sistem mereproduksi tindakan pengguna nyata sedekat mungkin.
Alat uji
Sekarang kita telah menetapkan tujuan yang ingin kita capai, mari kita bicara tentang alat apa yang telah kita pilih untuk ini.
▍TypeScript
Basis kode kami menggunakan TypeScript. Layanan backend kami ditulis dalam Go dan berinteraksi satu sama lain menggunakan gRPC. Ini memungkinkan kami untuk menghasilkan klien gRPC yang diketik untuk digunakan pada server GraphQL. Penyelesai server GraphQL diketik menggunakan jenis yang dihasilkan menggunakan
graphql-code-generator . Dan akhirnya, pertanyaan, mutasi, serta komponen dan kait langganan kami diketik sepenuhnya. Cakupan penuh basis kode kami dengan tipe menghilangkan seluruh kelas kesalahan yang disebabkan oleh fakta bahwa bentuk data tidak seperti yang diharapkan oleh programmer. Generasi jenis dari skema dan file protobuf memastikan bahwa seluruh sistem kami, di semua bagian tumpukan teknologi yang digunakan, tetap homogen.
▍Jest (pengujian unit)
Sebagai kerangka kerja untuk pengujian kode, kami menggunakan
Jest dan
@ testing-library / react . Dalam pengujian yang dibuat menggunakan alat ini, kami menguji fungsi atau komponen secara terpisah dari sistem lainnya. Kami biasanya menguji fungsi dan komponen yang paling sering digunakan dalam aplikasi, atau yang memiliki banyak cara untuk mengeksekusi kode. Jalur seperti itu sulit untuk diverifikasi selama integrasi atau pengujian ujung ke ujung (E2E).
Unit test bagi kami adalah sarana untuk menguji bagian-bagian kecil. Tes integrasi dan ujung ke ujung melakukan pekerjaan yang sangat baik untuk memeriksa sistem pada skala yang lebih besar, memungkinkan Anda untuk memeriksa keseluruhan tingkat kesehatan aplikasi. Tetapi kadang-kadang Anda perlu memastikan bahwa detail kecilnya berfungsi, dan menulis tes integrasi untuk semua kemungkinan penggunaan kode terlalu mahal.
Misalnya, kita perlu memeriksa apakah navigasi keyboard berfungsi dalam komponen yang bertanggung jawab untuk bekerja dengan daftar drop-down. Tetapi pada saat yang sama, kami tidak ingin memeriksa semua varian yang mungkin dari perilaku tersebut saat menguji seluruh aplikasi. Akibatnya, kami menguji navigasi secara menyeluruh dalam isolasi, dan saat menguji halaman menggunakan komponen yang sesuai, kami hanya memperhatikan pengecekan interaksi tingkat yang lebih tinggi.
Alat uji
▍Cress (uji integrasi)
Tes integrasi yang dibuat menggunakan
Cypress adalah inti dari sistem pengujian kami. Ketika kami mulai membuat portal StackPath, ini adalah tes pertama yang kami tulis, karena sangat berharga dengan biaya overhead yang sangat sedikit untuk pembuatannya. Cypress menampilkan seluruh aplikasi kami di browser dan menjalankan skrip pengujian. Seluruh frontend kami berfungsi dengan cara yang persis sama seperti ketika pengguna bekerja dengannya. Benar, lapisan jaringan sistem digantikan oleh mokami. Setiap permintaan jaringan yang biasanya sampai ke server GraphQL mengembalikan data bersyarat ke aplikasi.
Menggunakan tiruan untuk mensimulasikan lapisan jaringan aplikasi memiliki banyak kekuatan:
- Tes lebih cepat. Bahkan jika backend proyek sangat cepat, waktu yang diperlukan untuk mengembalikan respons terhadap permintaan yang dibuat selama seluruh rangkaian pengujian bisa sangat besar. Dan jika Moki bertanggung jawab untuk mengembalikan jawaban, jawabannya dikembalikan secara instan.
- Tes menjadi lebih andal. Salah satu kesulitan dalam melakukan pengujian lengkap dari ujung ke ujung suatu proyek adalah bahwa perlu untuk mempertimbangkan keadaan variabel dari jaringan dan data server, yang dapat berubah. Jika akses nyata ke jaringan disimulasikan menggunakan moxas, variabilitas ini menghilang.
- Sangat mudah untuk mereproduksi situasi yang membutuhkan pengulangan yang tepat dari kondisi tertentu. Misalnya, dalam sistem nyata, akan sulit untuk membuat permintaan tertentu gagal secara stabil. Jika Anda perlu memeriksa reaksi yang benar dari aplikasi untuk permintaan yang gagal, maka moki dengan mudah memungkinkan Anda untuk bermain situasi darurat.
Meskipun mengganti seluruh backend dengan mok tampaknya tugas yang menakutkan, semua data bersyarat diketik menggunakan jenis-jenis TypeScript yang sama yang digunakan dalam aplikasi. Artinya - data ini, setidaknya - dalam hal struktur, dijamin setara dengan apa yang akan dihasilkan backend normal. Selama sebagian besar pengujian, kami cukup tenang menggunakan kelemahan menggunakan mooks alih-alih panggilan server nyata.
Selain itu, programmer sangat senang bekerja dengan Cypress. Tes dijalankan di Cypress Test Runner. Deskripsi tes ditampilkan di sebelah kiri, dan aplikasi tes berjalan di elemen
iframe
utama. Setelah memulai tes, Anda dapat mempelajari tahapan masing-masing dan mencari tahu bagaimana aplikasi berperilaku pada satu waktu atau yang lain. Karena alat untuk menjalankan tes berjalan di peramban itu sendiri, Anda dapat menggunakan alat peramban pengembang untuk men-debug tes.
Saat menulis tes front-end, sering terjadi bahwa dibutuhkan banyak waktu untuk membandingkan apa yang dilakukan tes dengan keadaan DOM pada titik tertentu dalam tes. Cypress sangat menyederhanakan tugas ini, karena pengembang dapat melihat semua yang terjadi dengan aplikasi yang sedang diuji.
Berikut adalah klip video yang menunjukkan ini.
Tes ini dengan sempurna menggambarkan prinsip pengujian kami. Rasio nilai mereka dengan "harga" mereka cocok untuk kita. Tes dengan cara yang sama mereproduksi tindakan pengguna nyata yang berinteraksi dengan aplikasi. Dan hanya lapisan jaringan proyek digantikan oleh mokami.
▍Cypress (pengujian ujung ke ujung)
Tes E2E kami juga ditulis menggunakan Cypress, tetapi di dalamnya kami tidak menggunakan moki baik untuk mensimulasikan tingkat jaringan proyek atau untuk mensimulasikan hal lain. Saat melakukan tes, aplikasi mengakses server GraphQL nyata, yang berfungsi dengan contoh nyata layanan backend.
Pengujian end-to-end sangat berharga bagi kami. Faktanya adalah bahwa hasil pengujian tersebut yang memberi tahu kami apakah sesuatu berfungsi seperti yang diharapkan atau tidak. Tidak ada tiruan yang digunakan selama pengujian tersebut, sebagai hasilnya, aplikasi bekerja dengan cara yang persis sama seperti ketika digunakan oleh klien nyata. Namun, perlu dicatat bahwa tes end-to-end "lebih mahal" daripada yang lain. Mereka lebih lambat, lebih sulit untuk menulis, mengingat kemungkinan kegagalan jangka pendek selama implementasi mereka. Lebih banyak pekerjaan diperlukan untuk memastikan bahwa sistem tetap dalam kondisi yang diketahui sebelum menjalankan tes.
Tes biasanya perlu dijalankan pada saat sistem dalam kondisi yang dikenal. Setelah tes selesai, sistem beralih ke kondisi lain yang diketahui. Dalam kasus tes integrasi, tidaklah sulit untuk mencapai perilaku sistem ini, karena panggilan ke API digantikan oleh moka, dan, sebagai akibatnya, setiap tes berjalan di bawah kondisi yang diketahui dikendalikan oleh programmer. Tetapi dalam kasus E2E-tes, sudah lebih sulit untuk melakukan ini, karena data server gudang berisi informasi yang dapat berubah selama pengujian. Akibatnya, pengembang perlu menemukan beberapa cara untuk memastikan bahwa ketika tes dimulai, sistem akan berada dalam kondisi yang diketahui sebelumnya.
Di awal uji coba ujung-ke-ujung, kami menjalankan skrip yang, dengan melakukan panggilan langsung ke API, membuat akun baru dengan tumpukan, situs, beban kerja, monitor, dan sejenisnya. Setiap sesi pengujian menyiratkan penggunaan instance baru dari akun tersebut, tetapi segala sesuatu yang lain dari waktu ke waktu tetap tidak berubah. Script, setelah melakukan semua yang diperlukan, membentuk file yang berisi data yang digunakan untuk menjalankan tes (biasanya berisi informasi tentang instance identifiers dan domain). Hasilnya, skrip memungkinkan Anda untuk membawa sistem ke kondisi yang sebelumnya diketahui sebelum menjalankan tes.
Karena pengujian end-to-end "lebih mahal" daripada jenis pengujian lainnya, kami, dibandingkan dengan tes integrasi, menulis lebih sedikit tes end-to-end. Kami berusaha keras untuk memastikan bahwa tes mencakup fitur aplikasi penting. Sebagai contoh, ini mendaftarkan pengguna dan login mereka, membuat dan mengatur situs / beban kerja, dan sebagainya. Berkat tes integrasi yang luas, kami tahu bahwa secara umum, frontend kami berfungsi. Tetapi tes end-to-end diperlukan hanya untuk memastikan bahwa ketika menghubungkan frontend ke backend, sesuatu tidak terjadi sehingga tes lain tidak dapat mendeteksi.
Kontra dari strategi pengujian komprehensif kami
Meskipun kami sangat senang dengan pengujian dan stabilitas aplikasi, ada juga kelemahan menggunakan strategi pengujian yang komprehensif seperti kami.
Untuk mulai dengan, penerapan strategi pengujian seperti itu berarti bahwa semua anggota tim harus terbiasa dengan banyak alat pengujian, dan tidak hanya dengan satu. Semua orang perlu tahu Jest, @ testing-library / react dan Cypress. Tetapi pada saat yang sama, pengembang tidak hanya perlu mengetahui alat-alat ini. Mereka juga harus dapat membuat keputusan tentang situasi mana yang harus digunakan. Apakah layak untuk menguji beberapa peluang baru untuk menulis tes ujung ke ujung, atau apakah tes integrasi cukup? Apakah perlu, selain tes end-to-end atau integrasi, untuk menulis tes unit untuk memverifikasi detail kecil dari implementasi fitur baru ini?
Tidak diragukan lagi, ini, jadi, "membebani kepala" programmer kami, sambil menggunakan satu-satunya alat mereka tidak akan mengalami beban seperti itu. Biasanya kita mulai dengan tes integrasi, dan setelah itu, jika kita melihat bahwa fitur yang sedang dipelajari adalah sangat penting dan sangat tergantung pada bagian server proyek, kami menambahkan tes ujung ke ujung yang sesuai. Atau kita mulai dengan tes unit, melakukan ini jika kita percaya bahwa uji unit tidak akan dapat memverifikasi semua seluk beluk penerapan mekanisme tertentu.
Tentu saja, kita masih dihadapkan pada situasi di mana tidak jelas harus mulai dari mana. Tetapi, karena kita terus-menerus harus membuat keputusan mengenai pengujian, pola-pola situasi umum tertentu mulai muncul. Sebagai contoh, kami biasanya menguji sistem validasi formulir menggunakan unit testing. Ini dilakukan karena fakta bahwa selama pengujian Anda perlu memeriksa banyak skenario yang berbeda. Pada saat yang sama, semua orang di tim tahu tentang ini dan tidak membuang waktu merencanakan strategi pengujian ketika salah satu dari mereka perlu menguji sistem validasi formulir.
Kelemahan lain dari pendekatan yang kami gunakan adalah komplikasi dalam mengumpulkan data tentang cakupan kode dengan tes. Meskipun ini mungkin, itu jauh lebih rumit daripada dalam situasi di mana seseorang digunakan untuk menguji suatu proyek. Meskipun pengejaran jumlah kode yang indah melalui tes dapat menyebabkan penurunan kualitas tes, informasi tersebut sangat berharga dalam hal menemukan "lubang" di ruang uji yang digunakan. Masalah menggunakan beberapa alat pengujian adalah bahwa, untuk memahami bagian mana dari kode yang belum diuji, Anda perlu menggabungkan laporan tentang cakupan kode dengan tes yang diterima dari sistem yang berbeda. Itu mungkin, tetapi jelas jauh lebih sulit daripada membaca laporan yang dihasilkan oleh salah satu cara untuk pengujian.
Ringkasan
Saat menggunakan banyak alat pengujian, kami dihadapkan dengan tugas-tugas sulit. Tetapi masing-masing alat ini melayani tujuannya sendiri. Pada akhirnya, kami percaya bahwa kami melakukan hal yang benar dengan memasukkan mereka ke dalam sistem pengujian kode kami. Tes integrasi - ini adalah tempat terbaik untuk mulai membuat sistem pengujian pada awal pengerjaan aplikasi baru atau saat melengkapi pengujian proyek yang sudah ada. Akan bermanfaat untuk mencoba menambahkan tes end-to-end ke proyek sedini mungkin, memeriksa fitur-fitur paling penting dari proyek.
Ketika ada tes end-to-end dan integrasi di test suite, ini harus mengarah pada fakta bahwa pengembang akan menerima tingkat kepercayaan tertentu dalam pengoperasian aplikasi ketika ada perubahan yang dilakukan untuk itu. Jika, selama pekerjaan pada proyek, kesalahan mulai muncul di dalamnya yang tidak terdeteksi oleh tes, ada baiknya mempertimbangkan tes mana yang bisa menangkap kesalahan ini dan apakah penampilan kesalahan menunjukkan kekurangan dari seluruh sistem pengujian yang digunakan dalam proyek.
Tentu saja, kami tidak segera datang ke sistem pengujian kami saat ini. Selain itu, kami berharap bahwa sistem ini, seiring pertumbuhan proyek kami, akan berkembang. Tapi sekarang kami sangat menyukai pendekatan kami untuk pengujian.
Pembaca yang budiman! Strategi apa yang Anda ikuti dalam pengujian frontend? Alat pengujian frontend apa yang Anda gunakan?
