Menulis Kode yang Dapat Diuji
Diterbitkan: 2022-11-03Pengujian unit adalah instrumen penting dalam kotak peralatan pengembang perangkat lunak mana pun. Menulis tes unit relatif mudah ketika berurusan dengan basis kode yang mengikuti praktik, pola, dan prinsip desain perangkat lunak terbaik. Masalah sebenarnya muncul ketika mencoba menguji unit yang dirancang dengan buruk, kode yang tidak dapat diuji.
Blog ini akan membahas cara menulis lebih banyak kode yang dapat diuji unit dan pola serta praktik buruk mana yang harus dihindari untuk meningkatkan kemampuan pengujian.
Kode yang dapat diuji & tidak dapat diuji
Saat bekerja pada aplikasi skala besar yang perlu dipelihara dalam jangka panjang, kita harus mengandalkan pengujian otomatis untuk menjaga kualitas sistem secara keseluruhan tetap tinggi. Dibandingkan dengan pengujian integrasi, di mana Anda menguji beberapa unit secara keseluruhan, pengujian unit memiliki keuntungan karena cepat dan stabil. Cepat karena kami membuat instance, idealnya, hanya kelas yang diuji, dan stabil karena kami biasanya mengolok-olok dependensi eksternal, misalnya, database atau koneksi jaringan.
Jika Anda tidak terbiasa dengan perbedaan yang tepat antara pengujian unit dan integrasi, Anda dapat membaca lebih lanjut tentang topik ini di blog Pengantar pengujian kami.
Kode yang dapat diuji dapat diisolasi dari basis kode kami yang lain. Dengan kata lain, unit terkecil dapat diuji secara independen. Kode yang tidak dapat diuji ditulis sedemikian rupa sehingga sulit, atau bahkan tidak mungkin, untuk menulis unit test yang baik untuknya.
Mari kita tinjau beberapa anti-pola dan praktik buruk yang harus kita hindari saat menulis kode yang dapat diuji.
Contoh ditulis dalam Java, tetapi konvensi pengkodean yang disebutkan di sini berlaku untuk bahasa pemrograman berorientasi objek dan kerangka pengujian. Kami akan menggunakan assertj, dan JUnit5 untuk contoh di posting blog ini.
Injeksi ketergantungan
Injeksi ketergantungan adalah salah satu pola desain terpenting untuk mencapai isolasi uji. Injeksi ketergantungan adalah pola desain di mana satu objek menerima objek lain (dependensi) melalui parameter konstruktor atau setter alih-alih harus membangunnya sendiri.
Dengan injeksi dependensi, kita dapat dengan mudah mengisolasi kelas yang sedang diuji dengan mengejek dependensi suatu objek.
Mari kita lihat contoh tanpa injeksi ketergantungan:

Karena ketergantungan mesin sedang dibangun di konstruktor kelas Mobil, Anda dapat mengatakan bahwa kelas Mobil dan Mesin digabungkan dengan erat. Mereka sangat bergantung satu sama lain – mengubah satu akan membutuhkan perubahan pada yang lain.
Dari perspektif pengujian, Anda tidak dapat menguji kelas Mobil secara terpisah karena contoh di atas tidak dapat menggantikan implementasi Engine beton dengan uji ganda.
Namun, kita dapat mencapai isolasi dengan menggunakan injeksi ketergantungan dan polimorfisme:

Sekarang kita dapat membuat beberapa implementasi mesin dan, oleh karena itu, mobil dengan mesin yang berbeda:

Pengujian dalam isolasi sekarang dimungkinkan karena kami dapat membuat implementasi tiruan dari abstraksi Engine dan meneruskannya ke kelas Mobil kami:

Saat berhadapan dengan objek yang memerlukan objek lain (dependensi), Anda harus menyediakannya melalui parameter konstruktor (injeksi dependensi), idealnya tersembunyi di balik beberapa abstraksi.
Dengan mengikuti pola ini, kode Anda menjadi lebih mudah dibaca dan beradaptasi untuk berubah seiring waktu. Juga, Anda harus menghindari melakukan pekerjaan yang sebenarnya di konstruktor - apa pun yang lebih dari tugas lapangan adalah pekerjaan yang sebenarnya. Kata kunci `baru` dalam konstruktor selalu merupakan tanda peringatan dari kode yang tidak dapat diuji.
Satu hal yang perlu diperhatikan di sini adalah bahwa dalam beberapa situasi, kopling ketat (misalnya, kata kunci baru dalam konstruktor, kelas dalam untuk isolasi logika, pemetaan objek) bukanlah praktik yang buruk – kelas yang tidak masuk akal sebagai kelas "mandiri".
Keadaan global
Berbagi status global sering kali dapat menghasilkan tes yang tidak stabil (terkadang lulus, terkadang gagal), terutama di lingkungan multithread.
Bayangkan sebuah skenario di mana beberapa objek yang diuji berbagi status global yang sama – jika metode di salah satu objek memicu efek samping yang mengubah nilai status global bersama, output dari metode di objek lain menjadi tidak dapat diprediksi. Hindari menggunakan metode statis yang tidak murni karena metode tersebut mengubah status global dalam beberapa cara atau diproksikan ke beberapa status global.
Mari kita lihat metode statis tidak murni ini:

Pada dasarnya, metode ini membaca tanggal dan waktu sistem saat ini dan mengembalikan hasil berdasarkan nilai tersebut. Akan sangat sulit untuk menulis pengujian unit berbasis status yang tepat untuk metode ini karena panggilan statis LocalDateTime.now() akan menghasilkan hasil yang berbeda selama pelaksanaan pengujian kami. Menulis tes untuk metode ini tidak mungkin dilakukan tanpa mengubah tanggal dan waktu sistem.
Untuk memperbaikinya, kami akan meneruskan tanggal waktu ke metode timeOfDay sebagai argumen:


metode statis timeOfDay sekarang murni – input yang sama selalu menghasilkan hasil yang sama. Sekarang kita dapat dengan mudah melewatkan objek dateTime yang terisolasi sebagai argumen dalam pengujian kita:

Hukum Demeter
Hukum Demeter, atau prinsip pengetahuan paling sedikit, menyatakan bahwa objek hanya boleh tahu tentang objek yang terkait erat dengan objek pertama. Dengan kata lain, satu objek seharusnya hanya memiliki akses ke objek yang dibutuhkannya. Misalnya, kami memiliki metode yang menerima objek konteks sebagai argumen:

Metode ini melanggar Hukum Demeter karena perlu berjalan pada grafik objek untuk mendapatkan informasi yang diperlukan untuk melakukan pekerjaannya. Melewati informasi yang tidak perlu ke dalam kelas dan metode merusak kemampuan pengujian.
Bayangkan objek BillingContext besar yang berisi referensi ke objek lain:

Seperti yang bisa kita lihat, pengujian kita dipenuhi dengan informasi yang tidak penting. Tes yang membuat grafik objek kompleks sulit dibaca dan menimbulkan kerumitan yang tidak perlu.
Mari kita perbaiki contoh kita sebelumnya:

Anda harus selalu meneruskan dependensi langsung ke kelas dan metode Anda. Namun, meneruskan banyak argumen ke dalam metode juga bukan praktik yang baik – idealnya, Anda harus meneruskan dua argumen secara maksimal atau membungkus argumen yang terkait erat ke dalam objek data.
Tuhan keberatan
Objek Tuhan adalah objek yang mereferensikan banyak objek berbeda lainnya, memiliki lebih dari satu tanggung jawab, dan memiliki banyak alasan untuk berubah. Jika sulit untuk meringkas apa yang dilakukan kelas, atau jika meringkas mencakup kata “dan”, kelas kemungkinan memiliki lebih dari satu tanggung jawab.
Objek Tuhan sulit untuk diuji karena kita berurusan dengan banyak ketergantungan yang tidak terkait, mencampur berbagai tingkat abstraksi dan kekhawatiran, dan mereka menghasilkan banyak efek samping. Akibatnya, sulit untuk mencapai keadaan yang diinginkan untuk kasus pengujian kami.
Sebagai contoh:

UserService memiliki lebih dari satu tanggung jawab – mendaftarkan pengguna baru dan mengirim email. Saat menguji pendaftaran pengguna, kita perlu berurusan dengan layanan email dan sebaliknya:

Bayangkan sebuah UserService dengan lebih dari dua dependensi yang tidak terkait. Dependensi ini memiliki dependensinya sendiri, dan seterusnya. Kami akan berakhir dengan tes yang tidak dapat dibaca, dipenuhi dengan informasi yang tidak terkait, dan sangat sulit untuk dipahami. Oleh karena itu, setiap kelas seharusnya hanya memiliki satu tanggung jawab dan alasan untuk berubah. Kelas yang hanya memiliki satu alasan untuk berubah adalah salah satu dari lima prinsip desain perangkat lunak yang disebut prinsip tanggung jawab tunggal.
Anda dapat membaca lebih lanjut tentang prinsip-prinsip SOLID di sini.
Kesimpulan
Basis kode yang mengikuti praktik terbaik desain perangkat lunak membuat pengujian unit penulisan jauh lebih mudah dikelola.
Di sisi lain, bisa sangat menantang, atau terkadang bahkan tidak mungkin, untuk menulis tes unit kerja untuk basis kode menggunakan anti-pola yang disebutkan. Menulis kode yang dapat diuji dengan baik membutuhkan banyak latihan, disiplin, dan usaha ekstra. Keuntungan paling signifikan dari kode yang dapat diuji adalah kemudahan pengujian dan kemampuan untuk memahami, memelihara, dan memperluas kode itu.
Kami harap blog ini membantu Anda menulis kode yang dapat diuji.