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:

python
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 / b

Mari kita telaah bagian-bagian penting module di atas:

  1. Baris 1 dan 5 (class KalkulatorError / class PembagianNolError): dua class exception kustom mengikuti pola Chapter Membuat Custom Exceptions. PembagianNolError menjadi cara bagi() melaporkan pembagi nol.
  2. Baris 8–10 (def __init__): constructor menyimpan nilai pembagi sebagai atribut, sehingga test nanti bisa memeriksa detail kegagalan.
  3. Baris 13–18 (tambah / kurang): dua fungsi murni satu baris, cocok sebagai kuda latih test pertama.
  4. Baris 21–24 (bagi): fungsi punya dua jalur perilaku — jalur normal mengembalikan hasil bagi, jalur error membangkitkan PembagianNolError.

Pengujian Manual dengan assert #

Cara paling sederhana menguji module di atas adalah skrip berisi pernyataan assert:

python
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:

text
Semua pemeriksaan lolos

Skrip ini bekerja, tetapi coba lihat apa yang terjadi ketika satu ekspektasi salah. Simpan varian berikut sebagai cek_manual_gagal.py lalu jalankan ulang:

python
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):

text
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 6

Interpreter 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):

  1. Arrange — siapkan data dan kondisi awal yang dibutuhkan.
  2. Act — jalankan satu perilaku yang sedang dites.
  3. Assert — bandingkan hasil nyata dengan hasil yang diharapkan.
python
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_diharapkan

Komentar 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:

text
test_<fungsi>_<skenario>

Contoh penerapannya pada module kalkulator:

Nama testYang diverifikasi
test_tambah_angka_positiftambah() benar untuk masukan positif
test_tambah_angka_negatiftambah() benar untuk masukan negatif
test_bagi_dengan_nol_menolakbagi() menolak pembagi nol

Test Pyramid #

Tidak semua test sama harganya. Test pyramid memandu proporsi tiap jenis test dalam proyek:

Jenis testJumlahKecepatanContoh
Unit testBanyakmilidetikmenguji tambah() dan bagi() secara langsung
Integration testSedangdetikmenguji beberapa module bekerja bersama, misalnya kode dan database
End-to-end testSedikitlambatmenguji 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.