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:

python
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) == hasil

Mari kita telaah bagian-bagiannya:

  1. Argumen pertama "a, b, hasil": nama parameter yang dideklarasikan pada fungsi test, dipisah koma.
  2. Argumen kedua (list of tuple): setiap tuple adalah satu kasus — satu paket nilai lengkap untuk seluruh parameter.
  3. 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):

text
...........                                                              [100%]
11 passed in 0.02s

Sebelas test dari tiga file — padahal jumlah fungsinya jauh lebih sedikit.

Membaca Nama Kasus Otomatis #

Mode verbose menampilkan bagaimana pytest menamai setiap kasus:

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

python
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) == hasil

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

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

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

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

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

python
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.5

Test 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.