Program yang hidup akan terus berubah: fitur baru ditambahkan, bug diperbaiki, nama fungsi dirapikan. Setiap perubahan berpotensi merusak perilaku yang sudah berjalan baik, dan kerusakan itu sering tidak kita sadari sampai pengguna mengeluh. Test otomatis adalah jaring pengaman yang menjawab satu pertanyaan penting: “apakah kode masih berperilaku benar setelah perubahan kemarin?”. Pada Chapter ini kita pelajari alasan menulis test, cara menyusun satu test yang baik, dan konvensi penamaan yang membuat ratusan test tetap mudah dibaca.
Mengapa Kita Menulis Test #
Tiga manfaat test otomatis paling sering dirasakan di proyek nyata:
- Perlindungan dari regression. Regression adalah perilaku benar yang rusak kembali oleh perubahan baru. Test yang dijalankan ulang setiap saat menangkap kerusakan ini dalam hitungan detik, bukan berminggu-minggu kemudian.
- Keberanian melakukan refactoring. Refactoring mengubah struktur kode tanpa mengubah perilaku. Tanpa test, sekadar mengubah nama fungsi terasa menakutkan; dengan test, kita tinggal menjalankan ulang seluruh pemeriksaan setelah refactoring selesai.
- Dokumentasi yang hidup. Test menunjukkan cara pemakaian fungsi yang benar: masukan apa yang diterima, hasil apa yang diharapkan, dan masukan apa yang ditolak. Karena test selalu dijalankan, dokumentasi ini tidak pernah kedaluwarsa diam-diam.
Untuk berlatih, kita pakai satu module kecil yang akan menemani seluruh Section ini, kalkulator.py:
class KalkulatorError(Exception):
"""Error dasar untuk seluruh module kalkulator."""
class PembagianNolError(KalkulatorError):
"""Dibangkitkan saat pembagi bernilai nol."""
def __init__(self, pembagi):
super().__init__(f"pembagi {pembagi} tidak boleh nol")
self.pembagi = pembagi
def tambah(a, b):
return a + b
def kurang(a, b):
return a - b
def bagi(a, b):
if b == 0:
raise PembagianNolError(b)
return a / bMari kita telaah bagian-bagian penting module di atas:
- Baris 1 dan 5 (
class KalkulatorError/class PembagianNolError): dua class exception kustom mengikuti pola Chapter Membuat Custom Exceptions.PembagianNolErrormenjadi carabagi()melaporkan pembagi nol. - Baris 8–10 (
def __init__): constructor menyimpan nilaipembagisebagai atribut, sehingga test nanti bisa memeriksa detail kegagalan. - Baris 13–18 (
tambah/kurang): dua fungsi murni satu baris, cocok sebagai kuda latih test pertama. - Baris 21–24 (
bagi): fungsi punya dua jalur perilaku — jalur normal mengembalikan hasil bagi, jalur error membangkitkanPembagianNolError.
Pengujian Manual dengan assert #
Cara paling sederhana menguji module di atas adalah skrip berisi pernyataan assert:
from kalkulator import tambah, kurang, bagi
assert tambah(2, 3) == 5
assert tambah(-1, 1) == 0
assert kurang(10, 4) == 6
assert bagi(10, 2) == 5.0
print("Semua pemeriksaan lolos")Output yang Dihasilkan:
Semua pemeriksaan lolosSkrip ini bekerja, tetapi coba lihat apa yang terjadi ketika satu ekspektasi salah. Simpan varian berikut sebagai cek_manual_gagal.py lalu jalankan ulang:
from kalkulator import tambah
assert tambah(2, 3) == 6, "tambah(2, 3) seharusnya 6"Output yang Dihasilkan (baris File "..." di mesin Anda menampilkan path lengkap; di sini kita ringkas namanya):
Traceback (most recent call last):
File "cek_manual_gagal.py", line 3, in <module>
assert tambah(2, 3) == 6, "tambah(2, 3) seharusnya 6"
^^^^^^^^^^^^^^^^^
AssertionError: tambah(2, 3) seharusnya 6Interpreter Python menghentikan skrip pada kegagalan pertama — pemeriksaan di baris berikutnya tidak pernah dijalankan. Kita juga tidak mendapat ringkasan berapa pemeriksaan yang gagal, dan skrip harus dijalankan ulang secara manual setiap kali kode berubah. Keterbatasan inilah yang diselesaikan framework test pada Chapter Menulis Test dengan pytest.
Pola Arrange-Act-Assert #
Test yang baik punya struktur tiga tahap yang dikenal sebagai pola Arrange-Act-Assert (AAA):
- Arrange — siapkan data dan kondisi awal yang dibutuhkan.
- Act — jalankan satu perilaku yang sedang dites.
- Assert — bandingkan hasil nyata dengan hasil yang diharapkan.
from kalkulator import tambah
def test_tambah_mengembalikan_jumlah_benar():
# Arrange: siapkan data masukan
a = 2
b = 3
hasil_diharapkan = 5
# Act: panggil kode yang sedang dites
hasil = tambah(a, b)
# Assert: bandingkan hasil dengan harapan
assert hasil == hasil_diharapkanKomentar tiga fase ini biasanya hanya dipakai saat belajar; begitu polanya terbiasa, kebanyakan tim menghapusnya karena struktur sudah terbaca dari jarak antar baris.
Menamai Test #
Nama test adalah kalimat pertama yang dibaca orang ketika run gagal. Konvensi yang umum dipakai:
test_<fungsi>_<skenario>Contoh penerapannya pada module kalkulator:
| Nama test | Yang diverifikasi |
|---|---|
test_tambah_angka_positif | tambah() benar untuk masukan positif |
test_tambah_angka_negatif | tambah() benar untuk masukan negatif |
test_bagi_dengan_nol_menolak | bagi() menolak pembagi nol |
Test Pyramid #
Tidak semua test sama harganya. Test pyramid memandu proporsi tiap jenis test dalam proyek:
| Jenis test | Jumlah | Kecepatan | Contoh |
|---|---|---|---|
| Unit test | Banyak | milidetik | menguji tambah() dan bagi() secara langsung |
| Integration test | Sedang | detik | menguji beberapa module bekerja bersama, misalnya kode dan database |
| End-to-end test | Sedikit | lambat | menguji alur utuh dari sisi pengguna, misalnya lewat browser |
Titik manis proyek Python yang sehat adalah piramida dengan dasar unit test yang lebar. Seluruh Section ini fokus membangun dasar piramida tersebut.