Pernah dengar unit testing? Atau pernah membuat unit testing? Bagi sebagian developer, menulis unit test adalah hal yang menyebalkan, tetapi sebenarnya ia memiliki banyak manfaat. Yuk, kita bahas!
Pengertian Unit Testing
Unit testing adalah salah satu jenis pengujian perangkat lunak (software) yang berfokus pada pengujian unit-unit terkecil dalam sebuah sistem perangkat lunak. Biasanya, unit testing mencakup pengujian function, method, dan class.
Umumnya, unit testing adalah pengujian paling awal yang dilakukan oleh developer sebelum melakukan pengujian lain, seperti integration test, functional test, dan end-to-end test.
💻 Mulai Belajar Pemrograman
Belajar pemrograman di Dicoding Academy dan mulai perjalanan Anda sebagai developer profesional.
Daftar SekarangIlustrasi piramida di atas menunjukkan bahwa unit test adalah pengujian yang paling banyak jumlahnya dibanding integration test dan end-to-end (e2e) test. Namun, unit test juga adalah yang paling cepat dan berbiaya rendah. Hal ini karena unit test tidak bergantung dengan sistem lain dan hanya menguji function by function.
Tujuan Unit Testing
- Deteksi bug lebih awal: dengan melakukan unit testing, kita dapat menemukan bug lebih awal tanpa perlu menjalankan keseluruhan aplikasi. Bug yang ditemui di unit testing biasanya berhubungan dengan kesalahan logika bisnis, penulisan sintaks, dan kesalahan-kesalahan lain yang dapat membuat kebingungan di kemudian hari.
- Menulis kode lebih baik: dengan membuat kode yang dapat dites (testable), developer akan secara disiplin menulis kode rapi dan sesuai dengan tanggung jawabnya (menerapkan single responsibility principle). Kode yang testable adalah kode yang dipecah ke dalam unit-unit kecil dan memiliki fungsi spesifik sehingga mudah untuk diuji secara independen.
- Menghasilkan dokumentasi: sebab unit test biasanya ditulis dengan format given-what-then, hal ini juga sekaligus membuat developer memiliki dokumentasi lengkap mengenai cara fungsi tersebut digunakan, tahapan fungsi bekerja, dan hal yang diharapkan dari penggunaan fungsi tersebut. Hal ini sangat berguna, terutama ketika ada developer baru dalam tim pengembangan.
Karakteristik Kode yang Baik untuk Unit Test
Kode yang baik adalah kode yang mudah untuk diuji. Berikut adalah karakteristik kode yang testable.
Low-coupling
Dalam pengembangan perangkat lunak, coupling artinya adalah cara sebuah artifact (fungsi, method, atau kelas) bergantung pada artifact lain. Low-coupling berusaha untuk membuat sekecil mungkin keterpengaruhan oleh fungsi lain. Pada code dengan high-coupling, ketika fungsi yang terkait dengannya berubah, fungsi tersebut juga perlu diubah. Selain itu, kode juga akan sulit dites secara independen.
Contohnya dalam kode di bawah ini.
Dalam kode di atas, method renderProductList bergantung pada method getAllProducts pada class ProductComponent. Dalam contoh ini, ProductService dipanggil secara langsung dan perubahan pada ProductService akan memengaruhi langsung ProductComponent, yang dapat menyebabkan masalah scalability dan maintainability di masa depan.
Sekarang, coba lihat contoh kode di bawah ini
Pada kode di atas, method renderProductList masih bergantung pada method getAllProducts dalam class ProductComponent. Ia tidak dipanggil secara langsung, tetapi instance dari ProductService menjadi parameter saat menginstansiasi productComponent. Dengan ini, bahkan jika ingin mengganti implementasi ProductService dengan kelas yang berbeda secara keseluruhan, kita hanya perlu mengganti input dari parameter productService.
Pure Code/Pure Function
Singkatnya, pure function adalah fungsi yang tidak menggunakan ataupun menghasilkan efek samping terhadap data yang tidak berada dalam parameter dan scope fungsi tersebut.
Fungsi add di atas adalah contoh dari pure function karena hanya menghasilkan output berdasarkan parameternya dan tidak menyebabkan efek samping, seperti memodifikasi variabel di luar cakupan fungsi, melakukan I/O, atau mengubah status aplikasi.
Sementara fungsi addToTotal di atas adalah contoh dari impure function karena ketergantungannya pada variabel total yang berada di luar scope fungsi tersebut.
Dalam menulis kode yang testable, kode yang menerapkan pure function akan lebih mudah diuji karena perilaku dari fungsi tersebut lebih konsisten dan hanya bergantung pada parameter fungsinya saja.
Memisahkan Logic dan Presentation
Memisahkan kode yang fokus terhadap logika pengelolaan data dengan kode yang mengelola antarmuka pengguna (user interface) sangat disarankan untuk membuat kode yang testable.
Lapisan logika yang terpisah dapat diuji secara terisolasi dari presentation layer, memungkinkan unit test yang lebih baik dan efisien. Kita dapat melakukan pengujian terhadap fungsionalitas bisnis tanpa memperhatikan proses data ditampilkan.
Kode yang “Sederhana”
Kode sederhana di sini adalah kode dengan low cyclomatic complexity. Cyclomatic complexity adalah ukuran yang digunakan untuk mengukur kompleksitas sebuah fungsi atau metode berdasarkan kemungkinan jumlah jalur eksekusi di dalamnya.
Sebuah metode dengan cyclomatic complexity yang tinggi akan memerlukan lebih banyak kasus untuk diuji secara menyeluruh. Menurunkan kompleksitas kode akan membuat kode lebih mudah diuji, dibaca, dipelihara, dan bersih. Ini karena semakin sederhana sebuah fungsi atau metode, semakin mudah bagi pengembang untuk memahami alur eksekusinya dan menguji berbagai kemungkinan.
Karakteristik Unit Test yang Baik
Unit test yang baik pada umumnya memiliki karakteristik sebagai berikut.
- Cepat: Biasanya suatu sistem memiliki banyak sekali unit test dan jika unit test tidak terlalu cepat, butuh waktu yang panjang untuk dieksekusi. Oleh karena itu, unit test harus singkat dan to-the-point.
- Sederhana: Setiap unit test seharusnya fokus pada memverifikasi perilaku atau fungsionalitas spesifik (mengikuti aturan “one assertion per test“). Struktur tes Anda harus mengikuti pola AAA (arrange, act, assert) untuk menjaga kejelasan dan keterbacaan unit test yang dibuat. Pilih nama tes yang deskriptif, bermakna, tetapi tetap sederhana agar Anda lebih mudah memahami ribuan tes.
- Unit Tests Harus Dieksekusi secara Terisolasi: Isolasi kode adalah praktik yang sangat disarankan dalam membuat kode yang testable. Data input tes juga harus terkontrol, jadi hindari penggunaan data yang dihasilkan secara dinamis dan mungkin memengaruhi hasil tes.
- Hasil Tes Harus Sangat Konsisten: Semakin deterministik unit test Anda, semakin baik. Dengan kata lain, hasilnya harus selalu konsisten, tidak peduli terhadap perubahan yang dilakukan pada kode atau dalam urutan pengujian.
- Refaktor Unit Test secara Berkala: Perlakukan kode unit test dengan perhatian yang sama seperti kode sistem utama Anda. Refaktor tes ketika diperlukan untuk meningkatkan readability, maintainability, dan penggunaan best practices.
- Masukkan ke Pipeline CI: Masukkan unit test ke dalam pipeline continuous integration (CI) dan otomatiskan eksekusinya. Hal ini memastikan bahwa tes dijalankan secara teratur sehingga memberikan informasi sesegera mungkin mengenai potensi masalah pada codebase.
Test Driven Development
Salah satu pendekatan yang disarankan dalam testing adalah dengan test driven development (TDD), yaitu menulis terlebih dahulu test sebelum menulis kode sebenarnya. Alasannya berikut.
- Test the Test (Watch It Fail): Menulis sebelum implementasi memungkinkan pengembang untuk memverifikasi bahwa tes tersebut efektif. Dengan melihat tes gagal pada awalnya, pengembang dapat memastikan bahwa tes tersebut secara tepat mengevaluasi fungsionalitas yang dimaksud. Hal ini mencegah skenario ketika tes selalu lulus karena adanya kesalahan dalam penulisan kode itu sendiri sehingga memastikan bahwa tes tersebut menguji fungsi dengan benar.
- Better Developer Experience: TDD mendorong pengembang untuk memikirkan bahwa kode mereka akan digunakan sejak awal. Dengan mulai menggunakan tes, pengembang fokus pada perilaku dan antarmuka kode mereka sebelum mendalami detail implementasi teknisnya. Hal ini sering menghasilkan interface yang lebih bersih dan intuitif karena detail implementasi diabstraksi serta pengembang “dipaksa” berpikir dari perspektif pengguna.
- Clearer Requirements and Design: Menulis tes terlebih dahulu membantu untuk menjelaskan kebutuhan konkret dari pengguna sebelum mulai menuliskan kode. Ini memaksa pengembang untuk memikirkan secara mendalam tentang perilaku dan tujuan yang diharapkan dari kode yang akan ditulis serta menghasilkan implementasi yang lebih fokus dan koheren. Hal ini dapat mencegah kesalahpahaman dan penafsiran yang salah di kemudian hari.
- Ensures Test Coverage: Dengan mengadopsi pendekatan TDD, menulis pengujian menjadi bagian integral dari proses pengembangan dan bukan sesuatu yang dilakukan setelahnya. Menulis tes terlebih dahulu memastikan bahwa kode memiliki cakupan yang cukup dari awal. Dengan test driven development, pengembang lebih mungkin mencapai test coverage yang lebih tinggi dan menangkap bug potensial lebih awal dalam siklus pengembangan.
Test-driven development (TDD) adalah pendekatan pengembangan perangkat lunak yang sangat efektif dalam memastikan kualitas dan keandalan kode. Dengan menulis tes terlebih dahulu sebelum implementasi, TDD membantu memastikan bahwa kode berperilaku sesuai dengan keinginan dan memiliki cakupan pengujian yang memadai.
Mengoptimalkan Unit Testing
Ada beberapa hal yang bisa developer lakukan untuk mengoptimalkan penulisan unit test, di antaranya berikut.
- Gunakan Framework Testing yang Tepat: Pilihlah framework pengujian sesuai dengan bahasa dan teknologi yang digunakan dalam proyek Anda. Berbagai framework, seperti Jest untuk JavaScript, pytest untuk Python, atau JUnit untuk Java, menyediakan fitur-fitur yang memudahkan dalam menulis, menjalankan, dan menganalisis hasil pengujian.
- Jalankan Pengujian secara Otomatis: Otomatiskan proses pengujian unit sehingga ia dapat dijalankan dengan mudah dan cepat setiap kali ada perubahan dalam kode. Pengujian otomatis membantu untuk mendeteksi bug lebih cepat dan memastikan bahwa setiap perubahan tidak merusak fungsionalitas yang ada.
- Gabungkan dengan Pengujian Lain: Menggabungkan pengujian unit dengan lainnya, seperti pengujian integrasi dan end-to-end, dapat memberikan gambaran yang lebih lengkap tentang kualitas perangkat lunak Anda. Ini membantu dalam mengidentifikasi bug atau masalah yang mungkin terlewat saat hanya melakukan pengujian unit.
Unit test memegang peran penting dalam pengembangan perangkat lunak untuk memberikan jaminan kualitas yang lebih tinggi dan deteksi bug lebih dini. Ini juga memungkinkan kita untuk mengidentifikasi dan memperbaiki masalah dengan cepat, meningkatkan keandalan dan maintainability kode, serta memastikan bahwa fungsionalitas yang diimplementasikan sesuai dengan harapan. Jadi, hal ini sangat krusial dalam mencapai pengembangan perangkat lunak yang sukses.