Pengujian otomatis (testing) adalah kode yang memeriksa kode lain, sehingga perilaku fungsi terverifikasi setiap kali program diubah. Python menyediakan kerangka pengujian bawaan bernama unittest, sedangkan pytest menjadi kerangka pihak ketiga yang paling banyak dipakai, sekaligus mampu menjalankan pengujian unittest. Topik ini membuat satu pengujian pertama dengan unittest, menjalankannya dari terminal, mengenalkan metode assert miliknya, lalu membandingkannya dengan gaya pytest pada fungsi yang sama.
Pengujian pertama dengan unittest#
Pengujian unittest ditulis sebagai kelas turunan unittest.TestCase yang metodennya berawalan test_. Setiap metode menguji satu perilaku dengan metode assert*, dan kerangka inilah yang menemukan, menjalankan, serta merangkum seluruhnya. Berikut adalah modul yang diuji bersama berkas pengujiannya.
def luas_persegi_panjang(panjang, lebar):
if panjang <= 0 or lebar <= 0:
raise ValueError("dimensi harus positif")
return panjang * lebarBerkas pengujian mengimpor fungsi dari modul tersebut, sehingga keduanya harus berada pada direktori kerja yang sama.
# tidak-dijalankan: berkas pengujian dijalankan lewat perintah unittest dan pytest
import unittest
from hitung import luas_persegi_panjang
class TestLuas(unittest.TestCase):
def test_luas_biasa(self):
self.assertEqual(luas_persegi_panjang(2, 3), 6)
def test_dimensi_nol_ditolak(self):
with self.assertRaises(ValueError):
luas_persegi_panjang(0, 5)
if __name__ == "__main__":
unittest.main()Metode pertama membandingkan keluaran fungsi dengan nilai yang diharapkan, sedangkan metode kedua memeriksa bahwa ValueError muncul bila dimensinya tidak masuk akal, dan pemeriksaan exception juga termasuk pengujian yang sah. Perintah berikut menjalankan seluruh pengujian pada berkas tersebut dengan mode rinci.
# menjalankan pengujian unittest dengan keluaran rinci
python3 -m unittest test_hitung.py -vtest_dimensi_nol_ditolak (test_hitung.TestLuas.test_dimensi_nol_ditolak) ... ok
test_luas_biasa (test_hitung.TestLuas.test_luas_biasa) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OKDua baris pertama masing-masing menyebut satu pengujian yang berakhir ok, sedangkan rangkuman menyebut jumlah pengujian dan status akhirnya. Bila ada pengujian yang gagal, barisnya ditandai FAIL, dan traceback yang menjelaskan ketidakcocokannya dicetak sebelum rangkuman. Dengan cara ini, kerangka menggantikan pengecekan manual yang harus diulang setiap kali kode berubah. Bendera -v menyebut tiap pengujian secara eksplisit, dan tanpa bendera itu keluarannya hanya berupa titik per pengujian.
Keluarga metode assert#
Metode assert* adalah alat utama unittest, dan pemilihannya menentukan kualitas pesan kegagalan. Setiap metode menunjukkan perbedaan yang relevan bila pengujian gagal, misalnya assertEqual menampilkan kedua nilai yang tidak sama, sedangkan assertTrue hanya melaporkan bahwa sebuah pernyataan bernilai salah.
| Metode | Yang diperiksa |
|---|---|
assertEqual(a, b) | nilai a sama dengan b |
assertTrue(x) | x bernilai benar |
assertIn(a, b) | a menjadi anggota b |
assertIsNone(x) | x bernilai None |
assertAlmostEqual(a, b) | a dan b sama pada pembulatan tertentu |
assertRaises(E, f) | pemanggilan f menimbulkan exception E |
Metode assertAlmostEqual menangani jebakan bilangan desimal, karena 0.1 + 0.2 tidak persis sama dengan 0.3 dalam aritmetika biner, sehingga perbandingan eksaknya selalu gagal. Metode assertRaises digunakan dalam bentuk with seperti pada contoh sebelumnya, yang membungkus pemanggilan yang seharusnya menimbulkan exception. Semakin spesifik metode yang dipilih, semakin jelas pula pesan kegagalannya tanpa perlu membuka kembali kode sumbernya.
pytest menjalankan pengujian yang sama#
pytest adalah kerangka pihak ketiga yang dijalankan dengan perintah pytest, dan kekuatannya ada pada dua hal, yaitu menemukan pengujian secara otomatis serta membaca assert biasa dengan pesan kegagalan yang kaya. Pengujian unittest yang sudah ditulis dapat dijalankan oleh pytest tanpa perubahan apa pun.
# tidak-dijalankan: baris ringkasan pytest memuat durasi yang berubah antar eksekusi
pytest -q test_hitung.pyBlok di atas tidak disertai keluaran karena baris rangkumannya memuat durasi eksekusi yang berubah-ubah, sehingga tidak dapat dijadikan acuan yang tetap. Bentuk keluarannya ringkas pada mode -q, yaitu satu titik untuk setiap pengujian yang lulus beserta penanda persentase kemajuan, kemudian baris rangkumannya berisi 2 passed diikuti durasinya. Dua pengujian unittest dijalankan oleh pytest sebagai dua titik, yang menunjukkan keduanya berbagi aturan penemuan pengujian yang sama.
Menulis pengujian gaya pytest#
Pengujian gaya pytest tidak memerlukan kelas maupun impor unittest. Bentuknya cukup fungsi bernama test_ yang menggunakan assert biasa, dan pesan kegagalannya disusun ulang oleh pytest agar memuat kedua nilai yang dibandingkan.
def sapa(nama):
return f"Halo, {nama}!"
def test_sapa_biasa():
assert sapa("Ali") == "Halo, Ali!"
def test_sapa_kapital():
assert sapa("Ali").upper() == "HALO, ALI!"Berkas ini tetap dapat dijalankan dengan python3 test_sapa.py tanpa efek apa pun, karena tidak ada pemanggilan yang berjalan pada tingkat modul. Kondisi tersebut membuat berkas ini aman dikumpulkan kerangka pengujian. Perintah pytest untuk berkasnya sama seperti sebelumnya.
# tidak-dijalankan: alasan yang sama, yaitu durasi pada ringkasan pytest
pytest -q test_sapa.pyKeluarannya kembali berupa dua titik dan baris 2 passed dengan durasinya. Perbedaan utama terletak pada penulisannya, yaitu fungsi polos dengan assert yang sudah dikenal sejak awal jalur ini, sehingga pengujian pertama dapat ditulis tanpa mempelajari kelas dan metode assert* terlebih dahulu. Fitur pytest yang lebih lanjut seperti fixture dan parametrize dapat dipelajari nanti, ketika pengujian semacam ini terasa tidak mencukupi.
Memilih antara unittest dan pytest#
Keduanya menyelesaikan pekerjaan yang sama, dan pilihannya lebih banyak menyangkut lingkungan kerja daripada kemampuan teknis. Tabel berikut merangkum perbedaannya.
| Aspek | unittest | pytest |
|---|---|---|
| Asal | pustaka standar | pihak ketiga, dipasang dengan pip |
| Bentuk pengujian | kelas TestCase dengan metode assert* | fungsi test_ dengan assert biasa |
| Perintah jalannya | python3 -m unittest | pytest |
| Pengujian yang dikenali | kelas TestCase | kelas TestCase dan fungsi test_ |
unittest menjadi pilihan aman bila proyek harus berjalan tanpa dependensi tambahan, misalnya pada lingkungan yang ketat terhadap pemasangan package. pytest menjadi pilihan yang lebih ringkas pada penulisan sehari-hari, dan kemampuannya menjalankan pengujian unittest membuat peralihan bertahap tidak menuntut penulisan ulang. Banyak proyek memulai dengan unittest lalu berpindah perlahan, karena kedua gaya tetap dapat berdampingan pada satu perintah pytest.
Kesalahan umum#
Kesalahan pertama adalah menamai berkas atau metode pengujian di luar pola test_. Kerangka menemukan pengujian menurut pola nama, sehingga metode cek_luas di dalam kelas TestCase tidak pernah dijalankan, dan rangkuman pun melaporkan nol pengujian tanpa pesan kesalahan apa pun. Perbaikannya sederhana, yaitu memastikan berkasnya bernama test_*.py dan setiap metodenya berawalan test_.
Kesalahan kedua adalah menjalankan pengujian dari direktori yang salah. Impor from hitung import luas_persegi_panjang mengandalkan direktori kerja yang memuat hitung.py, sehingga perintah yang dijalankan dari lokasi lain berakhir dengan ModuleNotFoundError. Pemindahan berkas pengujian berdampingan dengan modul yang diuji, atau penataan direktori pengujian yang konsisten, menghilangkan kesalahan ini.
Kesalahan ketiga adalah menulis pengujian yang bergantung pada urutan jalannya pengujian lain, misalnya satu metode menyimpan data yang dipakai metode berikutnya. Urutan penemuan pengujian berbeda antar kerangka, sehingga ketergantungan semacam ini menghasilkan kegagalan yang muncul dan hilang tanpa pola. Setiap pengujian sebaiknya menyiapkan datanya sendiri dan berakhir pada keadaan yang bersih.
Ringkasan#
- Pengujian
unittestditulis sebagai kelasunittest.TestCasedengan metodetest_, dan dijalankan melaluipython3 -m unittestdari direktori kerja yang memuat berkasnya. - Metode
assert*menyatakan harapan pengujian, denganassertAlmostEqualuntuk bilangan desimal danassertRaisesuntuk exception yang diharapkan. - pytest menjalankan pengujian
unittestmaupun fungsitest_ber-assertbiasa melalui perintahpytest, dengan-quntuk keluaran ringkas. unittestdipilih bila dependensi tambahan tidak dimungkinkan, sedangkan pytest menawarkan penulisan yang lebih ringkas dan pesan kegagalan yang lebih kaya.
Referensi#
- unittest — Python Software Foundation — Referensi kerangka unittest beserta keluarga metode assert dan antarmuka baris perintahnya.
- pytest: helps write better programs — pytest project — Dokumentasi resmi pytest, termasuk aturan penemuan pengujian dan perintah menjalankannya.