Ketika satu fungsi harus benar untuk banyak kombinasi masukan, menulis satu test per kombinasi cepat menjadi membosankan: tiga test test_tambah dengan isi hampir identik, hanya angkanya yang berbeda. Duplikasi semacam ini menutupi pola yang sesungguhnya sama — dan setiap kasus baru menambah fungsi baru pula. pytest menutup masalah ini dengan decorator @pytest.mark.parametrize yang mengubah satu fungsi test menjadi banyak kasus sekaligus.
Parametrize Dasar #
Kita ingin memastikan tambah() benar untuk empat kombinasi masukan. Daripada empat fungsi, cukup satu:
import pytest
from kalkulator import tambah
@pytest.mark.parametrize(
"a, b, hasil",
[
(2, 3, 5),
(-1, 1, 0),
(0, 0, 0),
(100, 250, 350),
],
)
def test_tambah_mengembalikan_jumlah(a, b, hasil):
assert tambah(a, b) == hasilMari kita telaah bagian-bagiannya:
- Argumen pertama
"a, b, hasil": nama parameter yang dideklarasikan pada fungsi test, dipisah koma. - Argumen kedua (list of tuple): setiap tuple adalah satu kasus — satu paket nilai lengkap untuk seluruh parameter.
- Fungsi test menerima
a, b, hasil: pytest memanggil fungsi ini sekali per tuple, menyuntikkan nilai-nilainya seperti fixture.
Output yang Dihasilkan (run python3 -m pytest -q pada folder yang berisi tiga file test parametrize di Chapter ini):
........... [100%]
11 passed in 0.02sSebelas test dari tiga file — padahal jumlah fungsinya jauh lebih sedikit.
Membaca Nama Kasus Otomatis #
Mode verbose menampilkan bagaimana pytest menamai setiap kasus:
test_parametrize.py::test_tambah_mengembalikan_jumlah[2-3-5] PASSED [ 9%]
test_parametrize.py::test_tambah_mengembalikan_jumlah[-1-1-0] PASSED [ 18%]
test_parametrize.py::test_tambah_mengembalikan_jumlah[0-0-0] PASSED [ 27%]
test_parametrize.py::test_tambah_mengembalikan_jumlah[100-250-350] PASSED [ 36%]Bracket di akhir nama test adalah id kasus yang dibangun otomatis dari nilai tuple, dipisah tanda hubung. Saat satu kasus gagal, kita langsung tahu kombinasi masukannya dari nama ini — tanpa membuka file. Manfaat id ini paling terasa di log CI: satu baris FAILED ...[100-250-350] sudah menjelaskan kasus mana yang pecah, dan opsi pytest -k "bulat" bisa memilih ulang satu kasus spesifik untuk dijalankan terpisah.
Memberi Nama Kasus dengan ids #
Nilai mentah seperti -1 atau 100 tidak selalu bercerita. Argumen ids menggantinya dengan nama bermakna:
import pytest
from kalkulator import bagi
@pytest.mark.parametrize(
"a, b, hasil",
[
(10, 2, 5.0),
(7, 2, 3.5),
(-9, 3, -3.0),
],
ids=["bulat", "pecahan", "negatif"],
)
def test_bagi_mengembalikan_hasil(a, b, hasil):
assert bagi(a, b) == hasilOutput yang Dihasilkan (dipangkas dari run python3 -m pytest -v):
test_parametrize_ids.py::test_bagi_mengembalikan_hasil[bulat] PASSED [ 45%]
test_parametrize_ids.py::test_bagi_mengembalikan_hasil[pecahan] PASSED [ 54%]
test_parametrize_ids.py::test_bagi_mengembalikan_hasil[negatif] PASSED [ 63%]Parametrize pada Level Class #
Decorator yang sama bisa menempel pada class; seluruh method test di dalamnya dijalankan untuk setiap kombinasi:
import pytest
from kalkulator import tambah, kurang
@pytest.mark.parametrize(
"a, b",
[(10, 4), (8, 3)],
)
class TestOperasiKalkulator:
def test_tambah(self, a, b):
assert tambah(a, b) > a
def test_kurang(self, a, b):
assert kurang(a, b) < aOutput yang Dihasilkan (dipangkas dari run python3 -m pytest -v):
test_parametrize_kelas.py::TestOperasiKalkulator::test_tambah[10-4] PASSED [ 72%]
test_parametrize_kelas.py::TestOperasiKalkulator::test_tambah[8-3] PASSED [ 81%]
test_parametrize_kelas.py::TestOperasiKalkulator::test_kurang[10-4] PASSED [ 90%]
test_parametrize_kelas.py::TestOperasiKalkulator::test_kurang[8-3] PASSED [100%]Dua kombinasi dikalikan dua method menghasilkan empat test — cocok untuk verifikasi silang beberapa properti terhadap kumpulan masukan yang sama.
Kapan Tidak Memakai Parametrize #
Parametrize tepat ketika semua kasus berbagi satu perilaku dan hanya berbeda data. Ketika tiap kasus menguji perilaku berbeda, memaksakan satu parametrize justru menurunkan kejelasan:
import pytest
from kalkulator import bagi, PembagianNolError
# Dua perilaku berbeda: dua test terpisah lebih jelas
def test_bagi_dengan_nol_menolak():
with pytest.raises(PembagianNolError):
bagi(10, 0)
def test_bagi_normal_mengembalikan_hasil():
assert bagi(10, 4) == 2.5Test pertama memeriksa penolakan error, test kedua memeriksa hasil normal — menjadikannya satu tabel parametrize memerlukan kolom “harapan” campuran antara exception dan angka, dan laporan kegagalannya jadi sulit dibaca.
Dengan data berulang beres, kita menghadapi jenis ketergantungan yang berbeda: layanan eksternal yang tidak boleh ikut teruji — wilayah kerja mocking pada Chapter Mocking dan Patching.