pytest — Parametrizing tests
Menjalankan satu fungsi test terhadap banyak kombinasi input, dengan laporan kegagalan per kasus.
Intisari
@pytest.mark.parametrizemenjalankan satu fungsi test untuk tiap set input.- Tiap kasus dilaporkan sebagai test terpisah — kamu tahu persis kombinasi mana yang gagal.
- Beberapa dekorator bertumpuk menghasilkan seluruh kombinasi (perkalian kartesius).
- Beri nama kasus lewat
ids=supaya output-nya terbaca. pytest.param(..., marks=pytest.mark.xfail)untuk menandai kasus yang memang diketahui gagal.
Bentuk dasarnya
import pytest
@pytest.mark.parametrize("teks,jumlah", [
("satu dua", 2),
("satu", 1),
("", 0),
(" ", 0),
("satu dua tiga", 3),
])
def test_hitung_kata(teks, jumlah):
assert hitung_kata(teks) == jumlah
test_hitung_kata[satu dua-2] PASSED
test_hitung_kata[satu-1] PASSED
test_hitung_kata[-0] PASSED
test_hitung_kata[ -0] FAILED ← langsung terlihat kasus mana
test_hitung_kata[satu dua tiga-3] PASSED
Ini bukan sekadar menghemat baris. Kalau kelima kasus itu ditulis sebagai lima
assert dalam satu fungsi, kegagalan pertama menghentikan sisanya — kamu tidak
tahu apakah kasus lain juga rusak. Dengan parametrize, semuanya dijalankan dan dilaporkan
sendiri-sendiri.
Memberi nama kasus
@pytest.mark.parametrize("teks,harapan", [
("[email protected]", True),
("bukan-email", False),
("@tanpa-lokal.com", False),
("tanpa-domain@", False),
], ids=["valid", "tanpa-at", "tanpa-lokal", "tanpa-domain"])
def test_validasi_email(teks, harapan):
assert valid_email(teks) is harapan
test_validasi_email[valid] PASSED
test_validasi_email[tanpa-at] PASSED
Wajib untuk input yang panjang atau berisi karakter aneh — tanpa ids, nama testnya jadi tidak terbaca.
Kombinasi
@pytest.mark.parametrize("model", ["opus", "sonnet", "haiku"])
@pytest.mark.parametrize("streaming", [True, False])
def test_panggil(model, streaming):
... # 3 × 2 = 6 test dijalankan
Kasus yang diketahui gagal
@pytest.mark.parametrize("teks,jumlah", [
("satu dua", 2),
pytest.param("satu\tdua", 2, marks=pytest.mark.xfail(reason="tab belum ditangani")),
pytest.param("kasus-lambat", 1, marks=pytest.mark.slow),
])
def test_hitung_kata(teks, jumlah):
assert hitung_kata(teks) == jumlah
xfail mendokumentasikan bug yang sudah diketahui tanpa membuat suite-mu merah.
Kalau suatu saat kasus itu lulus, pytest melaporkannya sebagai XPASS —
pengingat bahwa xfail-nya sudah bisa dihapus.
Kasus dari file
import json
from pathlib import Path
KASUS = json.loads(Path("tests/data/kasus_chunking.json").read_text(encoding="utf-8"))
@pytest.mark.parametrize("kasus", KASUS, ids=lambda k: k["nama"])
def test_chunking(kasus):
hasil = potong(kasus["masukan"], ukuran=kasus["ukuran"])
assert len(hasil) == kasus["jumlah_harapan"]
Pola ini sangat pas untuk dataset eval. Simpan pertanyaan dan jawaban acuan sebagai JSON, lalu parametrize atasnya. Menambah kasus uji jadi soal mengedit file data — bukan menulis kode test baru.
Parametrize sebuah fixture
@pytest.fixture(params=["chroma", "memori"])
def vector_store(request, tmp_path):
if request.param == "chroma":
return ChromaStore(path=tmp_path)
return StoreDiMemori()
def test_simpan_dan_cari(vector_store):
vector_store.simpan("1", [0.1, 0.2, 0.3])
assert vector_store.cari([0.1, 0.2, 0.3], k=1) == ["1"]
Setiap test yang memakai fixture ini otomatis dijalankan dua kali — sekali per implementasi. Cara yang bagus untuk memastikan dua backend berperilaku sama.
Contoh nyata: menguji chunking
@pytest.mark.parametrize("teks,ukuran,tumpang,jumlah", [
("a" * 100, 50, 0, 2),
("a" * 100, 50, 10, 3),
("a" * 30, 50, 0, 1),
("", 50, 0, 0),
], ids=["pas-dua", "dengan-tumpang", "lebih-kecil", "kosong"])
def test_jumlah_chunk(teks, ukuran, tumpang, jumlah):
assert len(potong(teks, ukuran, tumpang)) == jumlah
@pytest.mark.parametrize("ukuran,tumpang", [(100, 20), (500, 50), (1000, 100)])
def test_chunk_tidak_kehilangan_teks(ukuran, tumpang):
teks = "kata " * 500
chunks = potong(teks, ukuran, tumpang)
gabung = "".join(chunks)
for kata in teks.split():
assert kata in gabung
Menjalankan sebagian
uv run pytest -k "valid" # kasus yang namanya mengandung "valid"
uv run pytest "tests/test_a.py::test_x[valid]" # satu kasus persis
uv run pytest -m "not slow" # lewati yang ditandai slow
uv run pytest --lf # hanya yang gagal terakhir kali
Mendaftarkan marker
[tool.pytest.ini_options]
markers = [
"slow: test yang lambat",
"eval: eval kualitas LLM, memanggil API asli",
"integrasi: butuh layanan eksternal",
]
addopts = "--strict-markers"
--strict-markers membuat marker yang salah ketik jadi error, bukan diam-diam
diabaikan. Tanpa itu, @pytest.mark.slwo tidak akan pernah cocok dengan filter mana pun.
Kapan parametrize tidak cocok
- Kasusnya punya alur logika yang berbeda-beda — tulis test terpisah
- Setup tiap kasus sangat berbeda — pakai fixture
- Daftar parameternya lebih rumit dari kode yang diuji — itu pertanda kelebihan
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.