Assertion adalah kalimat keputusan dalam test: “saya menyatakan kondisi ini benar, dan hentikan test jika ternyata salah”. Pada Chapter Menulis Test dengan pytest kita sudah melihat assert polos menghasilkan laporan kegagalan yang rinci. Chapter ini membedah mekanisme di balik laporan tersebut (assert rewriting), melengkapi kotak peralatan dengan pytest.approx untuk angka pecahan dan pytest.raises untuk jalur error, lalu menutup dengan rancangan jumlah assertion per test beserta jebakan assert di dalam fungsi bantu.

Assert Rewriting di Balik Layar #

Ketika pytest mengimpor file test, pytest menulis ulang setiap pernyataan assert menjadi versi yang menyimpan nilai subekspresi — mekanisme ini bernama assert rewriting. Manfaatnya terlihat ketika test gagal. Perhatikan dua test sengaja salah berikut:

python
from kalkulator import bagi

def test_diskon_keranjang():
    harga = 15000
    jumlah = 2
    total = harga * jumlah
    assert total * 0.9 == 26000

def test_kondisi_gabungan():
    stok = 3
    ambang = 5
    assert stok >= ambang and stok > 0

Output yang Dihasilkan (dipangkas dari run python3 -m pytest -q):

text
>       assert total * 0.9 == 26000
E       assert (30000 * 0.9) == 26000

>       assert stok >= ambang and stok > 0
E       assert (3 >= 5)

Baris berawalan E menampilkan lebih dari sekadar hasil True/False:

  1. Pada test pertama, total * 0.9 dicetak sebagai (30000 * 0.9) — pytest menyisipkan nilai total yang sudah dihitung, sehingga kita tahu total keranjang 30000 tanpa membuka debugger.
  2. Pada test kedua, kondisi gabungan hanya menampilkan subekspresi yang gagal, (3 >= 5); karena operator and berhenti pada kegagalan pertama, subekspresi stok > 0 tidak pernah dievaluasi.

Angka Pecahan dengan pytest.approx #

Aritmetika pecahan biner tidak bisa menyimpan 0.1 secara persis, sehingga tambah(0.1, 0.2) == 0.3 bernilai False. Perbandingan langsung seperti ini akan membuat test gagal padahal kode sebenarnya benar. pytest menyediakan objek pytest.approx sebagai pembanding berbasis toleransi:

python
import pytest
from kalkulator import tambah, bagi

def test_bagi_hasil_pecahan_presisi():
    assert bagi(1, 3) == pytest.approx(0.333, abs=0.001)

def test_bagi_sama_dengan_pecahan_murni():
    assert bagi(1, 3) == pytest.approx(1 / 3)

def test_tambah_dengan_tolerance():
    assert tambah(0.1, 0.2) == pytest.approx(0.3)

Tiga bentuk pemakaian di atas:

  1. abs=0.001 mengatur toleransi absolut manual — selisih hingga 0.001 dianggap sama.
  2. Tanpa argumen, pytest.approx memakai toleransi relatif otomatis, cocok membandingkan dua hasil hitung yang sama-sama berasal dari operasi pecahan.
  3. pytest.approx(0.3) menampung ketidakpresisian 0.1 + 0.2 yang sedikit menyimpang dari 0.3.

Jalankan python3 -m pytest -q pada file tersebut (bersama test error di bawah), hasilnya:

text
....
4 passed in 0.01s

Menyatakan Kegagalan dengan pytest.raises #

Assertion tidak selalu tentang nilai balik; kadang perilaku benar justru adalah menolak masukan. pytest.raises menjadi assertion untuk exception:

python
import pytest
from kalkulator import bagi, PembagianNolError

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

Blok with mengelilingi kode yang diharapkan gagal. Argumen match menerima pola regex yang dicocokkan ke pesan error — bila exception tidak muncul atau pesannya berbeda, test dinyatakan gagal. Pembahasan mendalam ada di Chapter Testing Exceptions.

Satu Test, Satu Assertion Logis #

Test ideal memverifikasi satu perilaku. Bukan berarti hanya satu baris assert — beberapa assert yang memeriksa aspek berbeda dari perilaku yang sama tetap sehat:

python
def test_total_keranjang():
    keranjang.tambah_item("Kopi", 25000, 2)
    assert keranjang.jumlah_item() == 2
    assert keranjang.total() == 50000

Menggabungkan banyak perilaku berbeda dalam satu test membuat laporan kegagalan kabur: test berhenti di assertion pertama yang salah, sementara perilaku setelahnya belum tentu benar. Bila kita perlu komentar “dan juga cek…” di tengah test, biasanya itu sinyal test harus dipecah.

Jebakan Assert di Fungsi Bantu #

Menaruh assert di dalam fungsi bantu menggeser lokasi pelaporan kegagalan. Perhatikan pola berikut:

python
from keranjang import KeranjangBelanja

def assert_total_valid(total, harapan):
    assert total == harapan, "total keranjang tidak sesuai"

def test_total_keranjang():
    keranjang = KeranjangBelanja()
    keranjang.tambah_item("Kopi", 25000, 2)
    assert_total_valid(keranjang.total(), 60000)

Output yang Dihasilkan (dipangkas dari run python3 -m pytest -q):

text
    def test_total_keranjang():
        keranjang = KeranjangBelanja()
        keranjang.tambah_item("Kopi", 25000, 2)
>       assert_total_valid(keranjang.total(), 60000)

test_helper_salah.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

total = 50000, harapan = 60000

    def assert_total_valid(total, harapan):
>       assert total == harapan, "total keranjang tidak sesuai"
E       AssertionError: total keranjang tidak sesuai
E       assert 50000 == 60000

test_helper_salah.py:4: AssertionError

Laporan kegagalan kini menunjuk baris 4 — baris di dalam fungsi bantu — bukan baris 9 tempat test memanggilnya. pytest tetap menampilkan kedua frame, tetapi membaca laporan butuh lompatan dua kali.

Dengan assertion yang tajam, kita bergerak ke persoalan berikutnya: menyiapkan data dan objek yang sama berulang kali di banyak test — pekerjaan fixtures pada Chapter Fixtures.