Artikel ini adalah bagian terakhir dari seri penerapan pola arsitektur MVI di Multiplatform Kotlin. Di dua bagian sebelumnya ( bagian 1 dan bagian 2 ), kami ingat apa itu MVI, membuat modul Kittens generik untuk memuat gambar kucing dan mengintegrasikannya ke dalam aplikasi iOS dan Android.
Pada bagian ini, kita akan membahas modul Kittens dengan tes unit dan integrasi. Kita akan belajar tentang batasan pengujian saat ini di Kotlin Multiplatform, mencari tahu cara mengatasinya, dan bahkan membuatnya bekerja untuk keuntungan kita.
Proyek sampel yang diperbarui tersedia di GitHub kami .
Prolog
Tidak ada keraguan bahwa pengujian merupakan langkah penting dalam pengembangan perangkat lunak. Tentu saja, ini memperlambat proses, tetapi pada saat yang sama:
- memungkinkan Anda untuk memeriksa kasus-kasus tepi yang sulit ditangkap secara manual;
- Mengurangi kemungkinan regresi saat menambahkan fitur baru, memperbaiki bug, dan memfaktorkan ulang;
- memaksa Anda untuk menguraikan dan menyusun kode Anda.
Sekilas, poin terakhir mungkin tampak seperti kerugian, karena butuh waktu. Namun, itu membuat kode lebih mudah dibaca dan bermanfaat dalam jangka panjang.
βMemang, rasio waktu yang dihabiskan untuk membaca versus menulis lebih dari 10 banding 1. Kami terus membaca kode lama sebagai bagian dari upaya untuk menulis kode baru. ... [Oleh karena itu,] membuatnya mudah dibaca membuat lebih mudah menulis. " - Robert C. Martin, "Clean Code: A Handbook of Agile Software Craftsmanship"
Kotlin Multiplatform memperluas kemampuan pengujian. Teknologi ini menambahkan satu fitur penting: setiap pengujian secara otomatis dilakukan pada semua platform yang didukung. Jika, misalnya, hanya Android dan iOS yang didukung, maka jumlah pengujian dapat dikalikan dua. Dan jika pada titik tertentu dukungan untuk platform lain ditambahkan, maka secara otomatis akan tercakup dalam pengujian.
Pengujian pada semua platform yang didukung itu penting karena mungkin terdapat perbedaan dalam perilaku kode. Misalnya, Kotlin / Native memiliki model memori khusus , Kotlin / JS juga terkadang memberikan hasil yang tidak terduga.
Sebelum melangkah lebih jauh, perlu disebutkan beberapa batasan pengujian di Multiplatform Kotlin. Yang terbesar adalah kurangnya library tiruan untuk Kotlin / Native dan Kotlin / JS. Ini mungkin tampak seperti kerugian besar, tetapi saya pribadi menganggapnya sebagai keuntungan. Menguji di Multiplatform Kotlin cukup sulit bagi saya: Saya harus membuat antarmuka untuk setiap dependensi dan menulis implementasi pengujiannya (palsu). Butuh waktu lama, tetapi pada titik tertentu saya menyadari bahwa menghabiskan waktu untuk abstraksi adalah investasi yang mengarah pada kode yang lebih bersih.
Saya juga memperhatikan bahwa modifikasi selanjutnya pada kode ini membutuhkan waktu lebih sedikit. Mengapa demikian? Karena interaksi kelas dengan dependensinya tidak dipaku (diolok-olok). Dalam kebanyakan kasus, cukup dengan memperbarui implementasi pengujiannya saja. Tidak perlu mendalami setiap metode pengujian untuk memperbarui tiruan. Akibatnya, saya berhenti menggunakan pustaka tiruan bahkan dalam pengembangan Android standar. Saya sarankan membaca artikel berikut: " Mengolok-olok tidak praktis - Gunakan yang palsu " oleh Pravin Sonawane .
Rencana
Mari kita ingat apa yang kita miliki di modul Kittens dan apa yang harus kita uji.
- KittenStore adalah komponen utama modul. Implementasi KittenStoreImpl- nya berisi sebagian besar logika bisnis. Ini adalah hal pertama yang akan kami uji.
- KittenComponent adalah fasad modul dan titik integrasi untuk semua komponen internal. Kami akan membahas komponen ini dengan tes integrasi.
- KittenView adalah antarmuka publik yang mewakili ketergantungan UI KittenComponent.
- KittenDataSource adalah antarmuka akses Web internal yang memiliki implementasi khusus platform untuk iOS dan Android.
Untuk pemahaman yang lebih baik tentang struktur modul, saya akan memberikan diagram UMLnya
:
- Menguji KittenStore
- Membuat implementasi uji KittenStore.Parser
- Membuat implementasi pengujian KittenStore.Network
- Menulis Tes Unit untuk KittenStoreImpl
- Membuat implementasi uji KittenStore.Parser
- Menguji KittenComponent
- Membuat implementasi pengujian KittenDataSource
- Buat Implementasi Uji KittenView
- Menulis Tes Integrasi untuk KittenComponent
- Membuat implementasi pengujian KittenDataSource
- Menjalankan tes
- kesimpulan
Pengujian Unit KittenStore
Antarmuka KittenStore memiliki kelas implementasinya sendiri - KittenStoreImpl. Inilah yang akan kami uji. Ini memiliki dua dependensi (antarmuka internal), yang didefinisikan langsung di kelas itu sendiri. Mari kita mulai dengan menulis implementasi pengujian untuk mereka.
Uji implementasi KittenStore.Parser
Komponen ini bertanggung jawab atas permintaan jaringan. Seperti inilah tampilan antarmukanya:
Sebelum menulis implementasi pengujian antarmuka jaringan, kita perlu menjawab satu pertanyaan penting: data apa yang dikembalikan server? Jawabannya adalah bahwa server mengembalikan sekumpulan tautan gambar secara acak, setiap kali kumpulan yang berbeda. Dalam kehidupan nyata, format JSON digunakan, tetapi karena kami memiliki abstraksi Parser, kami tidak peduli tentang format dalam pengujian unit.
Implementasi nyata dapat mengalihkan aliran, sehingga pelanggan dapat dibekukan di Kotlin / Native. Akan sangat bagus untuk memodelkan perilaku ini untuk memastikan kode menangani semuanya dengan benar.
Jadi, implementasi pengujian Jaringan kami harus memiliki fitur-fitur berikut:
- harus mengembalikan kumpulan baris berbeda yang tidak kosong untuk setiap permintaan;
- format tanggapan harus sama untuk Jaringan dan Parser;
- harus dapat mensimulasikan kesalahan jaringan (Mungkin harus selesai tanpa respons);
- harus memungkinkan untuk mensimulasikan format respon yang tidak valid (untuk memeriksa kesalahan dalam Parser);
- seharusnya mungkin untuk mensimulasikan penundaan respons (untuk memeriksa fase boot);
- harus dapat dibekukan di Kotlin / Native (untuk berjaga-jaga).
Implementasi pengujian itu sendiri mungkin terlihat seperti ini:
TestKittenStoreNetwork memiliki penyimpanan string (seperti server nyata) dan dapat menghasilkannya. Untuk setiap permintaan, daftar baris saat ini dikodekan menjadi satu baris. Jika properti "images" adalah nol maka Maybe akan berhenti, yang seharusnya dianggap sebagai kesalahan.
Kami juga menggunakan TestScheduler . Penjadwal ini memiliki satu fungsi penting: membekukan semua tugas yang masuk. Jadi, operator observOn, yang digunakan bersama dengan TestScheduler, akan membekukan downstream, serta semua data yang melewatinya, seperti dalam kehidupan nyata. Tetapi pada saat yang sama, multithreading tidak akan terlibat, yang menyederhanakan pengujian dan membuatnya lebih andal.
Selain itu, TestScheduler memiliki mode "pemrosesan manual" khusus yang memungkinkan kita menyimulasikan latensi jaringan.
Uji implementasi KittenStore.Parser
Komponen ini bertanggung jawab untuk mem-parsing tanggapan dari server. Inilah antarmukanya:
Jadi, apa pun yang diunduh dari web harus diubah menjadi daftar tautan. Jaringan kami hanya menggabungkan string menggunakan pemisah titik koma (;), jadi gunakan format yang sama di sini.
Berikut implementasi pengujiannya:
Seperti pada Jaringan, TestScheduler digunakan untuk membekukan pelanggan dan memeriksa kompatibilitasnya dengan model memori Kotlin / Native. Kesalahan pemrosesan respons disimulasikan jika string input kosong.
Tes unit untuk KittenStoreImpl
Kami sekarang memiliki implementasi uji semua dependensi. Sudah waktunya untuk tes unit. Semua unit test bisa ditemukan di repository , disini saya hanya akan memberikan inisialisasi dan beberapa test sendiri.
Langkah pertama adalah membuat contoh implementasi pengujian kami:
KittenStoreImpl menggunakan mainScheduler, jadi langkah selanjutnya adalah menimpanya:
Sekarang kita bisa menjalankan beberapa tes. KittenStoreImpl harus memuat gambar segera setelah pembuatan. Artinya, permintaan jaringan harus dipenuhi, tanggapannya harus diproses, dan status harus diperbarui dengan hasil baru.
Apa yang kita lakukan:
- menghasilkan gambar di Jaringan;
- membuat instance baru KittenStoreImpl;
- memastikan negara bagian berisi daftar string yang benar.
Skenario lain yang perlu kita pertimbangkan adalah mendapatkan KittenStore.Intent.Reload. Dalam kasus ini, daftar harus dimuat ulang dari jaringan.
Langkah-langkah pengujian:
- menghasilkan gambar sumber;
- buat instance KittenStoreImpl;
- menghasilkan gambar baru;
- send Intent.Reload;
- pastikan kondisinya berisi gambar baru.
Terakhir, mari kita uji skenario berikut: saat flag isLoading disetel saat gambar dimuat.
Kami telah mengaktifkan pemrosesan manual untuk TestScheduler - sekarang tugas tidak akan diproses secara otomatis. Ini memungkinkan kami untuk memeriksa status sambil menunggu jawaban.
Pengujian Integrasi KittenComponent
Seperti yang saya sebutkan di atas, KittenComponent adalah titik integrasi dari keseluruhan modul. Kami dapat menutupinya dengan tes integrasi. Mari kita lihat API-nya:
Ada dua dependensi, KittenDataSource dan KittenView. Kami memerlukan implementasi pengujian untuk ini sebelum kami dapat memulai pengujian.
Sebagai kelengkapan, diagram berikut menunjukkan aliran data di dalam modul:
Menguji implementasi KittenDataSource
Komponen ini bertanggung jawab atas permintaan jaringan. Ini memiliki implementasi terpisah untuk setiap platform, dan kami membutuhkan implementasi lain untuk pengujian. Seperti inilah tampilan antarmuka KittenDataSource:
TheCatAPI mendukung pagination, jadi saya langsung menambahkan argumen yang sesuai. Jika tidak, ini sangat mirip dengan KittenStore.Network, yang kami implementasikan sebelumnya. Satu-satunya perbedaan adalah kami harus menggunakan format JSON karena kami menguji kode nyata dalam integrasi. Jadi kami hanya meminjam ide implementasi:
Seperti sebelumnya, kami membuat daftar string berbeda yang dikodekan ke dalam larik JSON pada setiap permintaan. Jika tidak ada gambar yang dibuat, atau argumen permintaan salah, Mungkin hanya akan berhenti tanpa tanggapan.
Pustaka kotlinx.serialization digunakan untuk membentuk array JSON . Ngomong-ngomong, KittenStoreParser yang diuji menggunakannya untuk mendekode.
Menguji implementasi KittenView
Ini adalah komponen terakhir yang kita perlukan untuk implementasi pengujian sebelum kita dapat memulai pengujian. Inilah antarmukanya:
Ini adalah tampilan yang hanya mengambil model dan mengaktifkan peristiwa, jadi implementasi pengujiannya sangat sederhana:
Kita hanya perlu mengingat model terakhir yang diterima - ini akan memungkinkan kita untuk memeriksa kebenaran model yang ditampilkan. Kita juga bisa mengirimkan event atas nama KittenView menggunakan metode dispatch (Event), yang dideklarasikan di kelas AbstractMviView yang diwariskan.
Tes integrasi untuk KittenComponent
Kumpulan tes lengkap dapat ditemukan di repositori , di sini saya hanya akan memberikan beberapa yang paling menarik.
Seperti sebelumnya, mari kita mulai dengan membuat instance dependensi dan menginisialisasi:
Saat ini ada dua penjadwal yang digunakan untuk modul: mainScheduler dan computationScheduler. Kami perlu menggantinya:
Kami sekarang dapat menulis beberapa tes. Mari kita periksa skrip utama terlebih dahulu untuk memastikan gambar dimuat dan ditampilkan saat startup:
Tes ini sangat mirip dengan yang kami tulis ketika kami melihat tes unit untuk KittenStore. Hanya sekarang seluruh modul terlibat.
Langkah-langkah pengujian:
- menghasilkan link ke gambar di TestKittenDataSource;
- membuat dan menjalankan KittenComponent;
- pastikan tautan mencapai TestKittenView.
Skenario menarik lainnya: gambar perlu dimuat ulang saat KittenView mengaktifkan peristiwa RefreshTriggered.
Tahapan:
- menghasilkan tautan sumber ke gambar;
- membuat dan menjalankan KittenComponent;
- menghasilkan tautan baru;
- kirim Event.RefreshTriggered atas nama KittenView;
- pastikan tautan baru mencapai TestKittenView.
Menjalankan tes
Untuk menjalankan semua pengujian, kita perlu melakukan tugas Gradle berikut:
./gradlew :shared:kittens:build
Ini akan mengkompilasi modul dan menjalankan semua pengujian pada semua platform yang didukung: Android dan iosx64.
Dan inilah laporan liputan JaCoCo:
Kesimpulan
Pada artikel ini, kami membahas modul Kittens dengan pengujian unit dan integrasi. Desain modul yang diusulkan memungkinkan kami untuk mencakup bagian-bagian berikut:
- KittenStoreImpl - berisi sebagian besar logika bisnis;
- KittenStoreNetwork - bertanggung jawab atas permintaan jaringan tingkat tinggi;
- KittenStoreParser - bertanggung jawab untuk mengurai respons jaringan;
- semua transformasi dan koneksi.
Poin terakhir sangat penting. Anda dapat menutupinya berkat fitur MVI. Tanggung jawab tampilan ini adalah untuk menampilkan data dan acara pengiriman. Semua langganan, konversi, dan tautan dilakukan di dalam modul. Jadi, kami dapat mencakup semuanya dengan tes umum, kecuali tampilan itu sendiri.
Tes semacam itu memiliki keuntungan sebagai berikut:
- jangan gunakan API platform;
- dilakukan dengan sangat cepat;
- dapat diandalkan (jangan berkedip);
- berjalan di semua platform yang didukung.
Kami juga dapat menguji kode untuk kompatibilitas dengan model memori Kotlin / Native yang kompleks. Ini juga sangat penting karena kurangnya keamanan pada waktu pembuatan: kode hanya mogok saat waktu proses dengan pengecualian yang sulit untuk di-debug.
Semoga ini bisa membantu Anda dalam proyek Anda. Terima kasih telah membaca artikel saya! Dan jangan lupa untuk mengikuti saya di Twitter .
...
Latihan bonus
Jika Anda ingin bekerja dengan implementasi pengujian atau bermain dengan MVI, berikut adalah beberapa latihan langsung.
Memfaktorkan ulang KittenDataSource
Ada dua implementasi antarmuka KittenDataSource dalam modul: satu untuk Android dan satu lagi untuk iOS. Saya telah menyebutkan bahwa mereka bertanggung jawab atas akses jaringan. Tapi sebenarnya mereka memiliki fungsi lain: mereka menghasilkan URL untuk permintaan tersebut berdasarkan argumen input "batas" dan "halaman". Pada saat yang sama, kami memiliki kelas KittenStoreNetwork yang tidak melakukan apa pun kecuali mendelegasikan panggilan ke KittenDataSource.
Tugas: Pindahkan logika pembuatan permintaan URL dari KittenDataSourceImpl (di Android dan iOS) ke KittenStoreNetwork. Anda perlu mengubah antarmuka KittenDataSource sebagai berikut:
Setelah Anda selesai melakukannya, Anda perlu memperbarui pengujian Anda. Satu-satunya kelas yang perlu Anda sentuh adalah TestKittenDataSource.
Menambahkan pemuatan halaman
TheCatAPI mendukung pagination, jadi kami dapat menambahkan fungsionalitas ini untuk pengalaman pengguna yang lebih baik. Anda bisa mulai dengan menambahkan acara Event.EndReached baru untuk KittenView, setelah itu kode akan berhenti mengkompilasi. Kemudian Anda perlu menambahkan Intent.LoadMore yang sesuai, konversikan Event baru menjadi Intent, dan proses yang terakhir di KittenStoreImpl. Anda juga perlu memodifikasi antarmuka KittenStoreImpl.Network sebagai berikut:
Terakhir, Anda perlu memperbarui beberapa implementasi pengujian, memperbaiki satu atau dua pengujian yang ada, dan kemudian menulis beberapa pengujian baru untuk menutupi pagination.
