Penanganan error adalah fitur, bukan tambahan: bagi() yang menerima pembagi nol tanpa protes adalah bug yang menunggu waktu meledak di produksi. Karena itu jalur error layak diuji setelang jalur normal — dan caranya berbeda, karena nilai balik yang diperiksa bukan hasil hitungan melainkan exception yang dibangkitkan. Pada Chapter ini kita menguji exception dengan pytest.raises, memeriksa exception kustom beserta isinya, memastikan kode justru tidak error, lalu mencatat anti-pola yang membuat test error menjadi sia-sia.

Menguji Kode yang Seharusnya Gagal #

Sebagai konteks, inilah jalur error pada kalkulator.py yang sudah kita kenal dari awal Section:

python
class PembagianNolError(Exception):
    def __init__(self, pembagi):
        super().__init__(f"pembagi {pembagi} tidak boleh nol")
        self.pembagi = pembagi

def bagi(a, b):
    if b == 0:
        raise PembagianNolError(b)
    return a / b

Perilaku kerjanya bisa dibuktikan langsung:

python
from kalkulator import bagi, PembagianNolError

try:
    hasil = bagi(10, 0)
except PembagianNolError as e:
    print("pesan:", e)
    print("pembagi:", e.pembagi)

Output yang Dihasilkan:

text
pesan: pembagi 0 tidak boleh nol
pembagi: 0

Dalam test pytest, pembandingnya adalah pytest.raises sebagai context manager, dengan argumen match berisi pola regex:

python
import pytest
from kalkulator import bagi, PembagianNolError

def test_bagi_dengan_nol_menolak():
    with pytest.raises(PembagianNolError, match=r"tidak boleh nol"):
        bagi(10, 0)

def test_pesan_error_menyebut_pembagi():
    with pytest.raises(PembagianNolError, match=r"pembagi 0"):
        bagi(10, 0)

def test_bagi_dengan_string_menolak():
    with pytest.raises(TypeError, match=r"unsupported operand"):
        bagi(10, "nol")

Tiga aturan kerja pytest.raises:

  1. Exception harus muncul di dalam blok with — bila bagi(10, 0) selesai tanpa error, test justru gagal dengan pesan DID NOT RAISE.
  2. Tipe harus cocok persis atau mewarisi tipe yang dinyatakan — PembagianNolError cocok karena dituliskan eksplisit; Exception generik juga cocok, dan inilah sumber anti-pola yang kita bahas di bawah.
  3. match dicocokkan dengan re.search terhadap pesan error — penulisan r"..." disarankan agar backslash regex tidak ditafsir ulang oleh string biasa.

Memeriksa Isi Exception Kustom #

match cukup untuk memeriksa pesan. Exception kustom sering membawa data tambahan sebagai atribut — rancangan mengikuti Chapter Membuat Custom Exceptions. Objek exception yang tertangkap bisa diakses lewat klausa as:

python
def test_error_menyimpan_pembagi():
    with pytest.raises(PembagianNolError) as info:
        bagi(7, 0)
    assert info.value.pembagi == 0

Setelah blok with selesai, info.value berisi instance exception yang tertangkap, lengkap dengan atribut pembagi yang diisi constructor. Test semacam ini memastikan kontrak data error tetap stabil bagi kode pemanggil.

Memastikan Tidak Ada Exception #

Kebalikannya juga penting: memastikan jalur normal tidak error. Tidak perlu alat khusus — cukup panggil kodenya:

python
def test_bagi_normal_tidak_membangkitkan_error():
    hasil = bagi(10, 4)
    assert hasil == 2.5

Bila bagi() tanpa sengaja membangkitkan exception pada masukan normal, pytest menghentikan test dan menampilkan traceback exception itu — test otomatis gagal. Ketiadaan pytest.raises adalah pernyataan “kode ini harus selesai mulus”.

Output seluruh test di atas (run python3 -m pytest -q):

text
.....                                                                    [100%]
5 passed in 0.01s

Anti-Pola pada Test Exception #

Dua kebiasaan berikut membuat test error kehilangan daya verifikasinya:

python
import pytest
from kalkulator import bagi

# ANTI-POLA 1: tipe terlalu longgar
def test_bagi_dengan_nol_terlalu_longgar():
    with pytest.raises(Exception):
        bagi(10, 0)

# ANTI-POLA 2: try/except lalu diam
def test_bagi_dengan_nol_tanpa_verifikasi():
    try:
        bagi(10, 0)
    except Exception:
        pass
  1. Tipe Exception generik lulus untuk semua kegagalan — termasuk TypeError yang terjadi karena test salah menulis argumen. Test tidak lagi memastikan error yang benar; selalu nyatakan tipe paling spesifik (PembagianNolError), dan tambahkan match bila pesan berkontribusi pada kontrak.
  2. try/except lalu pass bahkan tidak memverifikasi apa pun: bila bagi() berhenti membangkitkan error, test tetap lulus karena jalur normal juga tidak menyentuh except. Verifikasi keberadaan exception adalah tugas pytest.raises, bukan try/except.

Test kini menjaga kedua jalur perilaku — hasil normal dan penolakan error. Pertanyaan berikutnya mengukur: berapa banyak kode kita yang benar-benar tersentuh test? Jawabannya di Chapter Code Coverage.