Birthday Sale! Up to 40% off unlimited courses & creative assets Birthday Sale! Save up to 40%!
Advertisement
  1. Code
  2. Python
Code

Menulis Unit Test Profesional dengan Python

by
Difficulty:IntermediateLength:LongLanguages:

Indonesian (Bahasa Indonesia) translation by Andy Nur (you can also view the original English article)

Pengujian adalah dasar dari pengembangan perangkat lunak yang solid. Ada banyak jenis testing, tetapi jenis yang paling penting adalah unit testing. Unit testing membermu banyak kepercayaan diri bahwa kamu dapat menggunakan karya yang telah teruji dengan baik sebagai primitif dan bergantung padanya ketika kamu menyusunnya untuk membuat program. Mereka meningkatkan inventaris kode tepercayamu di luar bawaan bahasa dan library standar. Selain itu, Python memberikan dukungan besar untuk penulisan unit test.

Contoh Menjalankan

Sebelum menyelami semua prinsip, heuristik, dan pedoman, mari kita lihat unit test yang representatif sedang beraksi. Class SelfDrivingCar adalah implementasi parsial dari logika mengemudi mobil yang bisa mengemudi sendiri. Ini sebagian besar berkaitan dengan mengendalikan kecepatan mobil. Ia mengetahui benda-benda di depannya, batas kecepatan, dan apakah benda itu sampai di tujuannya atau tidak.

Berikut ini adalah unit test untuk metode stop() untuk membangkitkan seleramu. Saya akan membahas detailnya nanti.

Pedoman Unit Testing

Melakukan

Menulis unit test yang baik adalah kerja keras. Menulis unit test membutuhkan waktu. Saat kamu membuat perubahan pada kodmu, biasanya kamu perlu mengubah test juga. Terkadang kamu memiliki bug dalam kode test-mu. Itu berarti kamu harus benar-benar berkomitmen. Manfaatnya sangat besar, bahkan untuk proyek-proyek kecil, tetapi mereka tidak gratis.

Jadilah Disiplin

Kamu harus disiplin. Bersikaplah konsisten. Pastikan test selalu lulus. Jangan biarkan test rusak karena kamu "tahu" kodenya OK.

Mengotomatisasikan

Untuk membantmu disiplin, kamu harus mengotomatisasi unit test-mu. Test harus berjalan secara otomatis pada titik-titik penting seperti pra-commit atau pra-deployment. Idealnya, sistem manajemen kontrol sumbemu menolak kode yang semua test-nya tidak lulus.

Kode Yang Belum Di-test Adalah Rusak Menurut Definisi

Jika kamu tidak mengujinya, kamu tidak bisa mengatakan itu berfungsi. Ini berarti kamu harus menganggapnya rusak. Jika ini kode yang kritis, jangan gunakan untuk produksi.

Latar Belakang

Apa itu Unit?

Unit untuk tujuan unit testing adalah file/modul yang berisi sekumpulan fungsi terkait atau class. Jika kamu memiliki file dengan beberapa kelas, kamu harus menulis unit test untuk setiap class.

Ke TDD atau Tidak ke TDD

Test-driven development adalah praktik di mana kamu menulis test sebelum kamu menulis kode. Ada beberapa manfaat dari pendekatan ini, tetapi saya sarankan untuk menghindarinya jika kamu memiliki disiplin untuk menulis test yang tepat nanti.

Alasannya adalah saya mendesain dengan kode. Saya menulis kode, melihatnya, menulis ulang, melihatnya lagi dan menulis ulang lagi dengan sangat cepat. Tes menulis pertama membatasi saya dan memperlambat saya.

Setelah saya selesai dengan desain awal, saya akan menulis tes segera, sebelum diintegrasikan dengan sisa sistem. Yang mengatakan, ini adalah cara yang bagus untuk memperkenalkan dirmu ke unit test, dan itu memastikan semua kodemu akan memiliki test.

Modul Unittest

Modul unittest dilengkapi dengan library standar Python. Ini memberikan class yang disebut TestCase, yang dapat berasal dari class-mu. Kemudian kamu dapat mengganti metode setUp() untuk menyiapkan perlengkapan test sebelum setiap test dan/atau metode class classSetUp() untuk menyiapkan perlengkapan test untuk semua test (tidak diatur ulang di antara masing-masing test). Ada metode tearDown() dan classTearDown() yang sesuai dengan yang bisa kamu timpa juga.

Berikut adalah bagian yang relevan dari class SelfDrivingCarTest kita. Saya hanya menggunakan metode setUp(). Saya membuat contoh SelfDrivingCar baru dan menyimpannya di self.car sehingga tersedia untuk setiap test.

Langkah selanjutnya adalah menulis metode test khusus untuk menguji kode yang sedang di test — class SelfDrivingCar dalam kasus ini — melakukan apa yang seharusnya dilakukan. Struktur metode test cukup standar:

  • Menyiapkan environment (opsional).
  • Menyiapkan hasil yang diharapkan.
  • Memanggil kode yang sedang di test.
  • Menyatakan bahwa hasil aktual sesuai dengan hasil yang diharapkan.

Perhatikan bahwa hasilnya tidak harus berupa output dari suatu metode. Ini bisa berupa perubahan status class, efek samping seperti menambahkan baris baru dalam database, menulis file atau mengirim email.

Sebagai contoh, metode stop() dari class SelfDrivingCar tidak mengembalikan apa pun, tetapi metode ini mengubah keadaan internal dengan menetapkan kecepatannya ke 0. Metode assertEqual() yang disediakan oleh class dasar TestCase digunakan di sini untuk memverifikasi bahwa pemanggilan stop() berfungsi seperti yang diharapkan.

Sebenarnya ada dua test di sini. Tes pertama adalah memastikan bahwa jika kecepatan mobil 5 dan stop() dipanggil, maka kecepatannya menjadi 0. Kemudian, test lain adalah untuk memastikan tidak ada yang salah jika memanggil stop() lagi ketika mobil sudah berhenti.

Nanti, saya akan memperkenalkan beberapa test lagi untuk fungsionalitas tambahan.

Modul Doctest

Modul doctest cukup menarik. Ini memungkinkamu menggunakan contoh kode interaktif dalam docstring-mu dan memverifikasi hasilnya, termasuk Ekspesi yang diajukan.

Saya tidak menggunakan atau merekomendasikan doctest untuk sistem skala besar. Unit testing yang tepat membutuhkan banyak pekerjaan. Kode test biasanya jauh lebih besar dari kode yang diuji. Docstring bukan media yang tepat untuk menulis tes yang komprehensif. Tapi itu keren. Berikut adalah fungsi faktorial dengan dokumen test:

Seperti yang kamu lihat, docstring jauh lebih besar daripada kode fungsinya. Itu tidak mendukung keterbacaan.

Menjalankan Test

Oke. Kamu menulis unit test-mu. Untuk sistem yang besar, kamu akan memiliki puluhan/ratusan/ribuan modul dan class di berbagai direktori. Bagaimana kamu menjalankan semua tes ini?

Modul unittest menyediakan berbagai fasilitas untuk mengelompokkan tes dan menjalankannya secara terprogram. Lihat Loading and Running Tests. Tetapi cara yang termudah adalah pendeteksi test. Opsi ini hanya ditambahkan dalam Python 2.7. Pra-2.7 Anda bisa menggunakan nose untuk menemukan dan menjalankan test. Nose memiliki beberapa keunggulan lain seperti menjalankan fungsi pengujian tanpa harus membuat class untuk kasus test-mu. Tetapi untuk tujuan artikel ini, mari tetap bersatu.

Untuk menemukan dan menjalankan tesy berbasis unittes-mu, cukup ketik pada command-line:

python -m unittest discover

unittest akan memindai semua file dan sub-direktori, menjalankan test apa pun yang ditemukannya, dan memberikan laporan yang bagus serta runtime. Jika kamu ingin melihat test apa yang sedang berjalan, kamu dapat menambahkan flag -v:

python -m unittest discover -v

Ada beberapa bendera yang mengendalikan operasi:

Cakupan Test

Cakupan test adalah bidang yang sering diabaikan. Cakupan berarti seberapa banyak kodemu yang benar-benar diuji oleh test-mu. Misalnya, jika kamu memiliki fungsi dengan pernyataan if-else dan kamu hanya menguji cabang if, maka kamu tidak tahu apakah cabang else berfungsi atau tidak. Dalam contoh kode berikut, fungsi add() memeriksa jenis argumennya. Jika keduanya bilangan bulat, ia hanya akan menambahkannya.

Jika keduanya adalah string, ia mencoba untuk mengubahnya menjadi bilangan bulat dan menambahkannya. Kalau tidak, itu menimbulkan eksepsi. Fungsi test_add() menguji fungsi add() dengan argumen yang merupakan bilangan bulat dan dengan argumen yang float dan memverifikasi perilaku yang benar dalam setiap kasusnya. Tetapi cakupan test-nya tidak lengkap. Kasus argumen string tidak diuji. Hasilnya, test berhasil, tetapi kesalahan ketik di cabang tempat argumen keduanya tidak ditemukan (lihat 'intg' di sana?).

Berikut hasilnya:

Unit Test Praktis

Menulis unit test pada kebutuhan industri tidak mudah atau sederhana. Ada beberapa hal yang perlu dipertimbangkan dan pertukaran yang harus dilakukan.

Desain untuk Testabilitas

Jika kodemu adalah apa yang disebut kode spaghetti formal atau bola lumpur besar di mana berbagai tingkat abstraksi dicampur bersama dan setiap bagian kode bergantung pada setiap bagian kode lainnya, kamu akan kesulitan mengujinya. Juga, setiap kali kamu mengubah sesuatu, kamu harus memperbarui banyak test juga.

Kabar baiknya adalah bahwa desain perangkat lunak yang tepat untuk keperluan umum adalah apa yang kamu butuhkan untuk testabilitas. Secara khusus, kode modular berfaktor dengan baik, di mana setiap komponen memiliki tanggung jawab yang jelas dan berinteraksi dengan komponen lain melalui antarmuka yang terdefinisi dengan baik, akan membuat penulisan unit yang baik menjadi menyenangkan.

Misalnya, class SelfDrivingCar kita bertanggung jawab untuk pengoperasian mobil tingkat tinggi: pergi, berhenti, navigasi. Ini memiliki metode calcul_distance_to_object_in_front() yang belum diimplementasikan. Fungsi ini mungkin harus diimplementasikan oleh sub-sistem yang benar-benar terpisah. Ini mungkin termasuk membaca data dari berbagai sensor, berinteraksi dengan mobil self-driving lainnya, setumpuk visi alat berat untuk menganalisis gambar dari beberapa kamera.

Mari kita lihat bagaimana ini bekerja dalam praktiknya. SelfDrivingCar akan menerima argumen yang disebut object_detector yang memiliki metode yang disebut calcul_distance_to_object_in_front(), dan itu akan mendelegasikan fungsi ini ke objek ini. Sekarang, tidak perlu untuk melakukan unit test ini karena object_detector bertanggung jawab (dan harus diuji) untuk itu. Kamu masih ingin melakukan unit test fakta bahwa kamu menggunakan object_detector dengan benar.

Biaya/Manfaat

Jumlah upaya yang kamu lakukan dalam pengujian harus dikorelasikan dengan biaya kegagalan, seberapa stabil kodenya, dan betapa mudahnya memperbaikinya jika masalah terdeteksi di telepon.

Sebagai contoh, class mobil self-driving kita sangat kritis. Jika metode stop() tidak berfungsi dengan baik, mobil self-driving kita mungkin membunuh orang, menghancurkan properti, dan menggagalkan seluruh pasar mobil self-driving. Jika kamu mengembangkan mobil self-driving, saya curiga unit test-mu untuk metode stop() akan sedikit lebih teliti daripada saya.

Di sisi lain, jika satu tombol di aplikasi we-mu pada halaman yang terkubur tiga tingkat di bawah halaman utammu berkedip sedikit ketika seseorang mengkliknya, kamu dapat memperbaikinya, tetapi mungkin tidak akan menambahkan unit test khusus untuk kasus ini. Ekonomi tidak membenarkannya.

Menguji Pola Pikir

Menguji pola pikir itu penting. Satu prinsip yang saya gunakan adalah bahwa setiap bagian dari kode memiliki setidaknya dua pengguna: kode lain yang menggunakannya dan test yang mengujinya. Aturan sederhana ini banyak membantu dengan desain dan dependensi. Jika kamu ingat bahwa kamu harus menulis test untuk kodemu, kamu tidak akan menambahkan banyak dependensi yang sulit untuk direkonstruksi selama testing.

Sebagai contoh, anggaplah kodemu perlu menghitung sesuatu. Untuk melakukan itu, perlu memuat beberapa data dari database, membaca file konfigurasi, dan berkonsultasi secara dinamis dengan beberapa REST API untuk informasi terbaru. Ini semua mungkin diperlukan karena berbagai alasan, tetapi memasukkan semua itu ke dalam satu fungsi akan membuatnya cukup sulit untuk unit test. Masih mungkin dengan mocking, tetapi jauh lebih baik untuk menyusun kodemu dengan benar.

Fungsi Murni

Kode termudah untuk di-test adalah fungsi murni. Fungsi murni adalah fungsi yang hanya mengakses nilai parameternya, tidak memiliki efek samping, dan mengembalikan hasil yang sama setiap kali dipanggil dengan argumen yang sama. Mereka tidak mengubah status programu, tidak mengakses sistem file atau jaringan. Manfaatnya terlalu banyak untuk dihitung di sini.

Mengapa mereka mudah diuji? Karena tidak perlu mengatur environment khusus untuk mengujinya. Kamu hanya melewati argumen dan menguji hasilnya. Kamu juga tahu bahwa selama kode yang diuji tidak berubah, test-mu tidak harus berubah.

Bandingkan dengan fungsi yang membaca file konfigurasi XML. Test-mu harus membuat file XML dan meneruskan nama file ke kode yang diuji. Bukan masalah besar. Tapi anggaplah seseorang memutuskan bahwa XML itu menjijikkan dan semua file konfigurasi harus dalam JSON. Mereka menjalankan bisnis mereka dan mengonversi semua file konfigurasi ke JSON. Mereka menjalankan semua test termasuk test-mu dan semuanya lulus!

Mengapa? Karena kodenya tidak berubah. Itu masih mengharapkan file konfigurasi XML, dan test-mu masih membangun file XML untuk itu. Tetapi dalam produksi, kodemu akan mendapatkan file JSON, yang gagal diurai.

Menguji Penanganan Kesalahan

Penanganan kesalahan adalah hal lain yang sangat penting untuk diuji. Itu juga bagian dari desain. Siapa yang bertanggung jawab atas kebenaran inputan? Setiap fungsi dan metode harus jelas tentangnya. Jika itu adalah tanggung jawab fungsi, ia harus memverifikasi inputnya, tetapi jika itu adalah tanggung jawab penelepon maka fungsi tersebut dapat menjalankan bisnisnya dan menganggap inputnya benar. Keseluruhan kebenaran sistem akan dipastikan dengan melakukan test untuk pemanggilnya agar memverifikasi bahwa itu hanya melewati inputan yang benar ke fungsimu.

Biasanya, kamu ingin memverifikasi input pada antarmuka publik ke kodmu karena kamu belum tentu tahu siapa yang akan memanggil kodemu. Mari kita lihat metode drive() dari mobil self-driving. Metode ini mengharapkan parameter "destination". Parameter "destination" akan digunakan nanti dalam navigasi, tetapi metode drive tidak melakukan apa pun untuk memverifikasi itu benar.

Mari kita asumsikan bahwa tujuan seharusnya adalah tuple dari garis lintang dan bujur. Ada semua jenis test yang dapat dilakukan untuk memverifikasinya valid (semisal. apakah tujuan di tengah laut). Untuk tujuan kita, mari kita pastikan itu adalah tuple float di kisaran 0,0 hingga 90,0 untuk garis lintang dan -180,0 hingga 180,0 untuk garis bujur.

Ini adalah class SelfDrivingCar yang diperbarui. Saya dengan sepele menerapkan beberapa metode yang tidak diimplementasikan karena metode drive() memanggil beberapa metode ini secara langsung atau tidak langsung.

Untuk menguji penanganan kesalahan dalam pengujian, saya akan memberikan argumen yang tidak valid dan memverifikasi bahwa mereka ditolak dengan benar. Kamu dapat melakukan ini dengan menggunakan self.assertRaises() metode unittest.TestCase. Metode ini berhasil jika kode yang diuji memang memunculkan eksepsi.

Mari kita lihat dalam aksi. Metode test_drive() melewati lintang dan bujur di luar rentang yang valid dan mengharapkan metode drive() untuk meningkatkan eksepsi.

Test gagal, karena metode drive() tidak memeriksa argumennya untuk validitas dan tidak memunculkan eksepsi. Kamu mendapatkan laporan yang bagus dengan informasi lengkap tentang apa yang gagal, di mana dan mengapa.

Untuk memperbaikinya mari kita perbarui metode drive() untuk benar-benar memeriksa rentang argumennya:

Sekarang, semua test lulus.

Menguji Metode Private

Haruskah kamu menguji setiap fungsi dan metode? Secara khusus, haruskah kamu menguji metode private yang hanya dipanggil oleh kodemu? Jawaban yang biasanya tidak memuaskan adalah: "Itu tergantung".

Saya akan mencoba untuk menjadi berguna di sini dan memberi tahumu apa itu tergantung. Kamu tahu persis siapa yang memanggil metode pribadimu — itu kodemu sendiri. Jika test-mu untuk metode public yang memanggil metode private-mu komprehensif maka kamu sudah menguji metode private-mu secara mendalam. Tetapi jika metode pribadi sangat rumit, kamu mungkin ingin mengujinya secara mandiri. Gunakan penilaiamu.

Cara Mengatur Unit Test-mu

Dalam sistem yang besar, tidak selalu jelas bagaimana mengatur test-mu. Haruskah kamu memiliki satu file besar dengan semua test untuk satu paket, atau satu file test untuk setiap class? Apakah test harus dalam file yang sama dengan kode yang diuji, atau di direktori yang sama?

Inilah sistem yang saya gunakan. Test harus sepenuhnya terpisah dari kode yang sedang diuji (karenanya saya tidak menggunakan doctest) Idealnya, kodmu harus dalam satu paket. Test untuk setiap paket harus di direktori saudara paketmu. Di direktori test, harus ada satu file untuk setiap modul paketmu bernama test_<module_name>.

Misalnya, jika kamu memiliki tiga modul dalam pakemu: module_1.py, module_2.py dan module_3.py, kamu harus memiliki tiga file test: test_module_1.py, test_module_2.py dan test_module_3.py di bawah direktori test.

Konvensi ini memiliki beberapa keunggulan. Itu membuatnya jelas hanya dengan menelusuri direktori bahwa kamu tidak lupa untuk menguji beberapa modul sepenuhnya. Ini juga membantu untuk mengatur test dalam potongan ukuran yang wajar. Dengan asumsi bahwa modulmu berukuran cukup maka kode test untuk setiap modul akan berada di file sendiri, yang mungkin sedikit lebih besar dari modul yang diuji, tetapi masih sesuatu yang cocok dengan nyaman dalam satu file.

Kesimpulan

Unit Test adalah dasar dari kode yang solid. Dalam tutorial ini, saya mengeksplorasi beberapa prinsip dan pedoman untuk unit testing dan menjelaskan alasan di balik beberapa praktik terbaik. Semakin besar sistem yang kamu bangun, menjadi semakin penting unit test. Tetapo unit test tidak cukup. Jenis tes lain juga diperlukan untuk sistem dengan skala besar:  integration tests, performance tests, load tests, penetration tests, acceptance tests,, dll.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.