IlmHamroh
Python kursi/Testlash4/10-dars19 daqiqa
Mundarija (22)

17.4-dars: parametrize

17-QISM — TESTLASH · 4-dars


1. Kirish va motivatsiya

O'tgan darslarda bir funksiyaning ko'p holatini tekshirish uchun har holatga alohida test yozdik: test_chegirmasiz, test_yarim_chegirma, test_toliq_chegirma. Ular deyarli bir xil — faqat kirish va kutilgan qiymat farq qiladi. Bu takror. Yechim — parametrize: bitta testni ko'p xil kirish-natija juftligi bilan avtomatik ishga tushirish.

parametrize — sinov holatlarini kod emas, ma'lumot sifatida ko'rsatish imkonini beradi: yangi holat qo'shish uchun bitta qator yozasiz, butun test funksiyasini emas.

Real vaziyat. Bir jamoa soliq hisoblovchi funksiyani test qilardi: har daromad oralig'i uchun boshqa foiz. Ular 12 ta alohida test funksiyasi yozishdi — har biri deyarli bir xil, faqat sonlar farqli, 60 qator takror kod. Yangi soliq oralig'i qo'shilganda yana test funksiyasi yozish kerak edi. parametrize ga o'tgach, 12 holat bitta jadvalga aylandi: bitta test funksiyasi, 12 qatorli ma'lumot ro'yxati. Yangi holat — bitta qator. Va har holat alohida test sifatida ishlaydi — bittasi yiqilsa, aynan qaysi kirish ekani ko'rinadi.

Bu darsda parametrize bilan bir testni ko'p ma'lumot bilan ishga tushirishni o'rganamiz.

Bu darsda:

  • @pytest.mark.parametrize — asosiy sintaksis
  • Har holat alohida test sifatida ishlaydi
  • pytest.param — id va belgilar (xfail)
  • Ko'p parametrni birlashtirish (stacking)
  • parametrize va fixture birga
  • Parametrlashtirilgan fixture (params=)
  • Chegara holatlarini jadval bilan qamrash
  • Amaliy: soliq va validatsiya jadvallari

2. Nazariya — chuqur tushuntirish

2.1. Asosiy parametrize

python
import pytest

@pytest.mark.parametrize("kirish, kutilgan", [
    (2, 4),
    (3, 9),
    (0, 0),
    (-2, 4),
])
def test_kvadrat(kirish, kutilgan):
    assert kirish ** 2 == kutilgan

Bu bitta test funksiyasidan to'rtta test yaratadi. Birinchi argument — vergul bilan ajratilgan parametr nomlari, ikkinchi — qiymatlar ro'yxati (har biri kortej).

2.2. Har holat — alohida test

parametrize har qatorni mustaqil test sifatida ishga tushiradi:

test_kvadrat[2-4] PASSED
test_kvadrat[3-9] PASSED
test_kvadrat[0-0] PASSED
test_kvadrat[-2-4] PASSED
Foyda Tafsilot
Aniq yiqilish Bittasi yiqilsa, aynan qaysi kirish ko'rinadi
Barchasi ishlaydi Bittasi yiqilsa, qolganlari to'xtamaydi
Sanoq to'g'ri 4 ta test, 1 emas

Bu — for sikl ichida assert dan yaxshiroq: siklda birinchi yiqilishda to'xtaydi va qaysi holat ekani noaniq.

2.3. pytest.param — id va belgilar

Har holatga o'qiladigan nom (id) va belgi (xfail, skip) berish uchun:

python
@pytest.mark.parametrize("kirish, kutilgan", [
    pytest.param(4, 2, id="ildiz-4"),
    pytest.param(9, 3, id="ildiz-9"),
    pytest.param(-1, None, marks=pytest.mark.xfail(reason="manfiy son")),
])
def test_ildiz(kirish, kutilgan):
    ...

id= test nomini o'qiladigan qiladi (test_ildiz[ildiz-4]); marks= bitta holatni belgilaydi (masalan hali ishlamaydigan holatni xfail).

2.4. Ko'p parametr (stacking)

Ikki parametrize ni ustma-ust qo'yish — barcha kombinatsiya:

python
@pytest.mark.parametrize("a", [1, 2])
@pytest.mark.parametrize("b", [10, 20])
def test_kombinatsiya(a, b):
    ...          # 2 × 2 = 4 test: (1,10), (1,20), (2,10), (2,20)

Kombinatsiyalar tez o'sadi (3 × 3 × 3 = 27) — faqat mantiqan zarur bo'lganda.

2.5. parametrize va fixture birga

parametrize fixture bilan birga ishlaydi — fixture parametr sifatida qo'shiladi:

python
@pytest.fixture
def baza():
    return {"koeff": 10}

@pytest.mark.parametrize("son", [1, 2, 3])
def test_hisob(baza, son):          # baza — fixture, son — parametr
    assert son * baza["koeff"] == son * 10

2.6. Parametrlashtirilgan fixture

Fixture ning o'zini parametrlash mumkin — u har qiymat uchun bir marta ishlaydi, va uni ishlatgan barcha test har qiymatda takrorlanadi:

python
@pytest.fixture(params=["sqlite", "postgres", "mysql"])
def baza_turi(request):
    return request.param            # request.param — joriy qiymat

def test_ulanish(baza_turi):        # 3 marta ishlaydi
    ...

Bu — bir testni turli konfiguratsiyalarda (masalan turli bazalarda) tekshirishning kuchli usuli.

2.7. Chegara holatlari jadvali

parametrize chegara holatlarini toza jadval bilan qamrashga ideal:

python
@pytest.mark.parametrize("yosh, yaroqli", [
    (0, True), (18, True), (150, True),       # chegaralar
    (-1, False), (151, False),                # tashqarida
    (17, True), (18, True),                   # atrofida
])
def test_yosh(yosh, yaroqli):
    assert yosh_yaroqli(yosh) == yaroqli

2.8. Qachon parametrize, qachon alohida test

parametrize Alohida test
Bir xil mantiq, farqli ma'lumot Farqli mantiq/tuzilma
Chegara holatlari jadvali Murakkab tayyorlash
Ko'p o'xshash holat Bir-ikki noyob holat

3. Tez ma'lumotnoma

python
import pytest

@pytest.mark.parametrize("kirish, kutilgan", [
    (2, 4),
    (3, 9),
])
def test_x(kirish, kutilgan):
    assert f(kirish) == kutilgan

# id va belgi bilan
@pytest.mark.parametrize("a, b", [
    pytest.param(1, 2, id="oddiy"),
    pytest.param(0, 0, marks=pytest.mark.xfail),
])
def test_y(a, b): ...

# parametrlashtirilgan fixture
@pytest.fixture(params=["a", "b"])
def variant(request):
    return request.param

Qoidalar

bir xil mantiq + farqli ma'lumot → parametrize
for-sikl + assert emas → parametrize (aniq yiqilish)
har holatga id= (o'qiladigan nom)
ishlamaydigan holat → marks=xfail
ko'p konfiguratsiya → params= fixture
kombinatsiya (stacking) ni ehtiyot bilan

4. Batafsil misollar

Misollarda pytest ni deterministik ishga tushiruvchi pytest_ishga yordamchisi ishlatiladi.

Misol 1 — Asosiy parametrize

python
"""bitta testdan ko'p test; har holat mustaqil ishlaydi va aniq yiqiladi; for-sikl bilan solishtirish; chegara holatlari jadvali."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


KOD = '''
def yosh_yaroqli(yosh):
    return 0 <= yosh <= 150
'''

TEST = '''
import pytest
from app import yosh_yaroqli

@pytest.mark.parametrize("kirish, kutilgan", [
    (2, 4),
    (3, 9),
    (0, 0),
    (-2, 4),
    (10, 100),
])
def test_kvadrat(kirish, kutilgan):
    assert kirish ** 2 == kutilgan

@pytest.mark.parametrize("yosh, yaroqli", [
    (0, True),
    (18, True),
    (150, True),
    (-1, False),
    (151, False),
    (75, True),
])
def test_yosh_chegaralari(yosh, yaroqli):
    assert yosh_yaroqli(yosh) == yaroqli
'''


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "app.py").write_text(KOD, encoding="utf-8")
        (r / "test_p.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Har holat alohida test (-v) ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ 5 + 6 = 11 test bitta funksiyalar juftligidan yaratildi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Har holat alohida test (-v) ===
  test_p.py::test_kvadrat[2-4] PASSED
  test_p.py::test_kvadrat[3-9] PASSED
  test_p.py::test_kvadrat[0-0] PASSED
  test_p.py::test_kvadrat[-2-4] PASSED
  test_p.py::test_kvadrat[10-100] PASSED
  test_p.py::test_yosh_chegaralari[0-True] PASSED
  test_p.py::test_yosh_chegaralari[18-True] PASSED
  test_p.py::test_yosh_chegaralari[150-True] PASSED
  test_p.py::test_yosh_chegaralari[-1-False] PASSED
  test_p.py::test_yosh_chegaralari[151-False] PASSED
  test_p.py::test_yosh_chegaralari[75-True] PASSED
  --- 11 passed ---
  chiqish kodi: 0
  ⭐ 5 + 6 = 11 test bitta funksiyalar juftligidan yaratildi

Nima ko'rsatdi: 2.1, 2.2, 2.7-bo'limlar.

Misol 2 — pytest.param: id, belgilar va aniq yiqilish

python
"""pytest.param bilan o'qiladigan id; marks=xfail bitta holatga; yiqilgan holat aynan qaysi kirish ekani ko'rinadi (for-sikldan farqi)."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


KOD = '''
def sonni_toifala(son):
    if son < 0:
        raise ValueError("manfiy son")
    if son == 0:
        return "nol"
    return "musbat" if son > 0 else "manfiy"
'''

TEST = '''
import pytest
from app import sonni_toifala

@pytest.mark.parametrize("son, kutilgan", [
    pytest.param(5, "musbat", id="musbat-son"),
    pytest.param(0, "nol", id="nol"),
    pytest.param(100, "musbat", id="katta-musbat"),
    pytest.param(-3, None, id="manfiy-xato", marks=pytest.mark.xfail(reason="ValueError kutilgan")),
])
def test_toifalash(son, kutilgan):
    assert sonni_toifala(son) == kutilgan

@pytest.mark.parametrize("son, kutilgan", [
    pytest.param(2, 4, id="teng"),
    pytest.param(3, 10, id="ATAYLAB-NOTOGRI"),   # 9 kerak, 10 yozdik
])
def test_kvadrat_bittasi_yiqiladi(son, kutilgan):
    assert son ** 2 == kutilgan
'''


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "app.py").write_text(KOD, encoding="utf-8")
        (r / "test_param.py").write_text(TEST, encoding="utf-8")

        print("=== 1. id va marks bilan ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no")
        for satr in satrlar:
            if "::" in satr or satr.startswith("FAILED") or satr.startswith("---"):
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ yiqilgan holat aynan qaysi kirish (ATAYLAB-NOTOGRI) ekani ko'rinadi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. id va marks bilan ===
  test_param.py::test_toifalash[musbat-son] PASSED
  test_param.py::test_toifalash[nol] PASSED
  test_param.py::test_toifalash[katta-musbat] PASSED
  test_param.py::test_toifalash[manfiy-xato] XFAIL (ValueError kutilgan)
  test_param.py::test_kvadrat_bittasi_yiqiladi[teng] PASSED
  test_param.py::test_kvadrat_bittasi_yiqiladi[ATAYLAB-NOTOGRI] FAILED
  --- short test summary info ---
  FAILED test_param.py::test_kvadrat_bittasi_yiqiladi[ATAYLAB-NOTOGRI] - assert...
  --- 1 failed, 4 passed, 1 xfailed ---
  chiqish kodi: 1
  ⭐ yiqilgan holat aynan qaysi kirish (ATAYLAB-NOTOGRI) ekani ko'rinadi

Nima ko'rsatdi: 2.2, 2.3-bo'limlar.

Misol 3 — Stacking, fixture va parametrlashtirilgan fixture

python
"""ikki parametrize stacking (kombinatsiya); parametrize + fixture birga; params= fixture bir testni turli konfiguratsiyada takrorlaydi."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


TEST = '''
import pytest

# 1. Stacking — barcha kombinatsiya
@pytest.mark.parametrize("soni", [1, 2])
@pytest.mark.parametrize("narx", [100, 200])
def test_jami(narx, soni):
    assert narx * soni > 0

# 2. parametrize + fixture
@pytest.fixture
def soliq_stavkasi():
    return 0.12

@pytest.mark.parametrize("daromad, soliqsiz", [
    (1000, 880.0),
    (2000, 1760.0),
    (0, 0.0),
])
def test_soliqdan_keyin(soliq_stavkasi, daromad, soliqsiz):
    assert daromad * (1 - soliq_stavkasi) == pytest.approx(soliqsiz)

# 3. parametrlashtirilgan fixture
@pytest.fixture(params=["json", "csv", "xml"])
def format(request):
    return request.param

def test_har_format(format):
    assert format in {"json", "csv", "xml"}
'''


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "test_kombinatsiya.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Barcha testlar (-v) ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Barcha testlar (-v) ===
  test_kombinatsiya.py::test_jami[100-1] PASSED
  test_kombinatsiya.py::test_jami[100-2] PASSED
  test_kombinatsiya.py::test_jami[200-1] PASSED
  test_kombinatsiya.py::test_jami[200-2] PASSED
  test_kombinatsiya.py::test_soliqdan_keyin[1000-880.0] PASSED
  test_kombinatsiya.py::test_soliqdan_keyin[2000-1760.0] PASSED
  test_kombinatsiya.py::test_soliqdan_keyin[0-0.0] PASSED
  test_kombinatsiya.py::test_har_format[json] PASSED
  test_kombinatsiya.py::test_har_format[csv] PASSED
  test_kombinatsiya.py::test_har_format[xml] PASSED
  --- 10 passed ---
  chiqish kodi: 0

Nima ko'rsatdi: 2.4, 2.5, 2.6-bo'limlar.

Misol 4 — Amaliy: soliq va validatsiya jadvallari

Progressiv soliq hisoblovchi funksiyani test qilamiz: daromad oralig'iga qarab boshqa foiz. Bu — aynan parametrize uchun tug'ilgan holat: ko'p oralik, har biri kirish-natija juftligi. Sinov holatlarini jadval sifatida yozamiz, chegaralarni (oralik chegaralari) alohida qamraymiz va xato yo'llarini marks bilan belgilaymiz.

python
"""progressiv soliq: parametrize jadvali bilan barcha oralik va chegaralar; id bilan o'qiladigan; xato yo'llari; validatsiya jadvali."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path

KOD = '''
def soliq_hisobla(daromad):
    """Progressiv soliq: 0-1000 → 0%, 1000-5000 → 10%, 5000+ → 20% (oshiqcha qismga)."""
    if daromad < 0:
        raise ValueError("daromad manfiy bo'la olmaydi")
    soliq = 0.0
    if daromad > 5000:
        soliq += (daromad - 5000) * 0.20
        daromad = 5000
    if daromad > 1000:
        soliq += (daromad - 1000) * 0.10
    return round(soliq, 2)

def yosh_toifasi(yosh):
    if not isinstance(yosh, int):
        raise TypeError("yosh butun son bo'lishi kerak")
    if yosh < 0:
        raise ValueError("yosh manfiy bo'la olmaydi")
    if yosh < 18:
        return "voyaga_yetmagan"
    if yosh < 65:
        return "kattalar"
    return "pensioner"
'''

TEST = '''
import pytest
from soliq import soliq_hisobla, yosh_toifasi

@pytest.mark.parametrize("daromad, soliq", [
    pytest.param(0, 0.0, id="daromad-yoq"),
    pytest.param(1000, 0.0, id="birinchi-oralik-chegarasi"),
    pytest.param(1001, 0.1, id="ikkinchi-oralik-boshi"),
    pytest.param(5000, 400.0, id="ikkinchi-oralik-oxiri"),
    pytest.param(6000, 600.0, id="uchinchi-oralik"),
    pytest.param(10000, 1400.0, id="katta-daromad"),
])
def test_soliq(daromad, soliq):
    assert soliq_hisobla(daromad) == pytest.approx(soliq)

def test_manfiy_daromad():
    with pytest.raises(ValueError, match="manfiy"):
        soliq_hisobla(-100)

@pytest.mark.parametrize("yosh, toifa", [
    pytest.param(0, "voyaga_yetmagan", id="chaqaloq"),
    pytest.param(17, "voyaga_yetmagan", id="17-chegara"),
    pytest.param(18, "kattalar", id="18-chegara"),
    pytest.param(64, "kattalar", id="64-chegara"),
    pytest.param(65, "pensioner", id="65-chegara"),
    pytest.param(100, "pensioner", id="keksa"),
])
def test_yosh_toifasi(yosh, toifa):
    assert yosh_toifasi(yosh) == toifa

@pytest.mark.parametrize("notogri", [
    pytest.param("yigirma", id="satr", marks=pytest.mark.xfail(raises=TypeError, strict=True)),
    pytest.param(-5, id="manfiy", marks=pytest.mark.xfail(raises=ValueError, strict=True)),
])
def test_notogri_yosh(notogri):
    yosh_toifasi(notogri)
'''


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


def main() -> None:
    with tempfile.TemporaryDirectory() as katalog:
        r = Path(katalog)
        (r / "soliq.py").write_text(KOD, encoding="utf-8")
        (r / "test_soliq.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Soliq va yosh jadvallari (-v) ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"\n  chiqish kodi: {kod}")
        print("  ⭐ o'nlab holat qisqa jadval bilan — yangi holat = bitta qator")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Soliq va yosh jadvallari (-v) ===
  test_soliq.py::test_soliq[daromad-yoq] PASSED
  test_soliq.py::test_soliq[birinchi-oralik-chegarasi] PASSED
  test_soliq.py::test_soliq[ikkinchi-oralik-boshi] PASSED
  test_soliq.py::test_soliq[ikkinchi-oralik-oxiri] PASSED
  test_soliq.py::test_soliq[uchinchi-oralik] PASSED
  test_soliq.py::test_soliq[katta-daromad] PASSED
  test_soliq.py::test_manfiy_daromad PASSED
  test_soliq.py::test_yosh_toifasi[chaqaloq] PASSED
  test_soliq.py::test_yosh_toifasi[17-chegara] PASSED
  test_soliq.py::test_yosh_toifasi[18-chegara] PASSED
  test_soliq.py::test_yosh_toifasi[64-chegara] PASSED
  test_soliq.py::test_yosh_toifasi[65-chegara] PASSED
  test_soliq.py::test_yosh_toifasi[keksa] PASSED
  test_soliq.py::test_notogri_yosh[satr] XFAIL
  test_soliq.py::test_notogri_yosh[manfiy] XFAIL
  --- 13 passed, 2 xfailed ---

  chiqish kodi: 0
  ⭐ o'nlab holat qisqa jadval bilan — yangi holat = bitta qator

Nima ko'rsatdi: 2.1, 2.3, 2.7-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Ko'p holat uchun ko'p test funksiyasi kerak" Bitta parametrize jadvali yetarli
"for sikl ichida assert bir xil" Birinchi yiqilishda to'xtaydi, holat noaniq
"parametrize bitta test" Har qator alohida test — alohida natija
"Holatga nom berib bo'lmaydi" pytest.param(..., id="...")
"Bitta holatni xfail qilib bo'lmaydi" pytest.param(..., marks=...)
"Ikki parametrize ketma-ket ishlaydi" Barcha kombinatsiya (dekart ko'paytma)
"parametrize fixture bilan ishlamaydi" Ishlaydi — ikkalasi parametr
"Fixture ni parametrlab bo'lmaydi" @pytest.fixture(params=...)

6. Keng tarqalgan xatolar va yechimlari

1. Takroriy test funksiyalari

python
def test_kvadrat_2(): assert 2**2 == 4        # ❌ takror
def test_kvadrat_3(): assert 3**2 == 9
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])   # ✅
def test_kvadrat(x, y): assert x**2 == y

2. for sikl ichida assert

python
def test_hammasi():                           # ❌ birinchi yiqilishda to'xtaydi
    for x, y in [(2, 4), (3, 9)]:
        assert x**2 == y
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])   # ✅ har biri alohida
def test_kvadrat(x, y): assert x**2 == y

3. Parametr nomlari mos kelmasa

python
@pytest.mark.parametrize("kirish, kutilgan", [(2, 4)])
def test_x(a, b): ...                         # ❌ nomlar mos emas
def test_x(kirish, kutilgan): ...             # ✅

4. Bitta parametrga kortej shart emas

python
@pytest.mark.parametrize("x", [(1,), (2,)])   # ❌ ortiqcha kortej
@pytest.mark.parametrize("x", [1, 2])         # ✅ bitta parametr — oddiy qiymat

5. Kombinatsiyalarni haddan oshirish

python
@pytest.mark.parametrize("a", range(10))      # ❌ 10×10×10 = 1000 test
@pytest.mark.parametrize("b", range(10))
@pytest.mark.parametrize("c", range(10))
# ✅ faqat mantiqan zarur kombinatsiyalar

6. id siz o'qib bo'lmaydigan nomlar

python
@pytest.mark.parametrize("d", [{"a": 1, "b": 2}])   # test_x[d0] — noaniq
@pytest.mark.parametrize("d", [pytest.param({"a": 1}, id="oddiy")])  # ✅

7. Xato holatini oddiy qiymat bilan

python
@pytest.mark.parametrize("x, y", [(-1, "xato")])    # ❌ istisno kutilsa noaniq
# ✅ marks=pytest.mark.xfail(raises=ValueError)

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 17.2-dars (o'tilgan): belgilar — marks= bilan birga
  • 17.3-dars (o'tilgan): fixture — parametrize bilan birga
  • 17.10-dars: hypothesis — property-based test (avtomatik ko'p kirish)
  • 20.15-dars: API endpointlarini turli kirish bilan test qilish
  • 24-qism: ma'lumot tahlili funksiyalarini ko'p holatda test qilish

8. Eng yaxshi amaliyotlar

  1. Bir xil mantiq, farqli ma'lumot — parametrize jadvali.

  2. for sikl + assert o'rniga parametrize (aniq yiqilish).

  3. Har holatga o'qiladigan id= bering.

  4. Ishlamaydigan/xato holatlarni marks= bilan belgilang.

  5. Chegara holatlarini jadval bilan to'liq qamrang.

  6. Kombinatsiyalarni (stacking) faqat mantiqan zarur bo'lganda.

  7. Turli konfiguratsiyalar uchun params= fixture.

  8. Jadvalni o'qiladigan tuting — sinov holatlari hujjat vazifasini ham bajaradi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

Har parametrize nechta test yaratishini ayting:

python
1.  @pytest.mark.parametrize("x", [1, 2, 3])
    def test_a(x): ...
2.  @pytest.mark.parametrize("x, y", [(1, 2), (3, 4)])
    def test_b(x, y): ...
3.  @pytest.mark.parametrize("a", [1, 2])
    @pytest.mark.parametrize("b", [10, 20, 30])
    def test_c(a, b): ...
4.  @pytest.mark.parametrize("x", [])
    def test_d(x): ...
5.  @pytest.fixture(params=["a", "b"])
    def f(request): return request.param
    def test_e(f): ...
6.  @pytest.mark.parametrize("x", [1, pytest.param(2, marks=pytest.mark.skip)])
    def test_f(x): ...          # nechta ishlaydi (skip emas)?
7.  @pytest.mark.parametrize("x", [1, 2])
    @pytest.mark.parametrize("y", [1, 2])
    @pytest.mark.parametrize("z", [1, 2])
    def test_g(x, y, z): ...
8.  @pytest.mark.parametrize("x, kutilgan", [(2, 4), (3, 9)])
    def test_h(x, kutilgan): assert x*x == kutilgan   # nechta PASSED?
Javoblar
  1. 3
  2. 2
  3. 6 (2 × 3)
  4. 0 — bo'sh ro'yxat, hech qanday test
  5. 1 test funksiyasi × 2 param = 2
  6. 2 test yaratiladi, 1 ishlaydi (1 skip)
  7. 8 (2 × 2 × 2)
  8. 2 PASSED

Vazifa 2: Xatolarni tuzating

python
1.  def test_kvadrat():
        for x, y in [(2, 4), (3, 9), (4, 16)]:
            assert x ** 2 == y

2.  @pytest.mark.parametrize("kirish, kutilgan", [(2, 4)])
    def test_x(a, b):
        assert a ** 2 == b

3.  @pytest.mark.parametrize("x", [(1,), (2,), (3,)])
    def test_musbat(x):
        assert x > 0

4.  # xato holatini test qilish
    @pytest.mark.parametrize("yosh, natija", [(-1, "xato")])
    def test_yosh(yosh, natija):
        assert yosh_toifasi(yosh) == natija

5.  @pytest.mark.parametrize("konfig", [
        {"host": "a", "port": 1},
        {"host": "b", "port": 2},
    ])
    def test_ulanish(konfig): ...       # test nomlari o'qib bo'lmaydi
Javoblar
python
1.  @pytest.mark.parametrize("x, y", [(2, 4), (3, 9), (4, 16)])
    def test_kvadrat(x, y):
        assert x ** 2 == y

2.  @pytest.mark.parametrize("kirish, kutilgan", [(2, 4)])
    def test_x(kirish, kutilgan):       # nomlar mos
        assert kirish ** 2 == kutilgan

3.  @pytest.mark.parametrize("x", [1, 2, 3])   # bitta param — oddiy qiymat
    def test_musbat(x):
        assert x > 0

4.  @pytest.mark.parametrize("yosh", [
        pytest.param(-1, marks=pytest.mark.xfail(raises=ValueError, strict=True)),
    ])
    def test_yosh_xato(yosh):
        yosh_toifasi(yosh)

5.  @pytest.mark.parametrize("konfig", [
        pytest.param({"host": "a", "port": 1}, id="host-a"),
        pytest.param({"host": "b", "port": 2}, id="host-b"),
    ])
    def test_ulanish(konfig): ...

Vazifa 3: Validatsiya jadvali

Email validatori uchun to'liq parametrize jadvali:

  1. Yaroqli emaillar (oddiy, nuqta, pastki chiziq, raqam)
  2. Yaroqsiz (@ yo'q, domen yo'q, bo'shliq, bo'sh)
  3. Har holatga o'qiladigan id
  4. pytest -v da har biri alohida ko'rinsin

Vazifa 4: Kombinatsiya testlari

To'lov tizimi (valyuta × usul × summa):

  1. parametrize stacking bilan kombinatsiyalar
  2. Ba'zi kombinatsiyalar mumkin emas — marks=xfail
  3. Kombinatsiyalar sonini hisoblang va oqilona chegaralang
  4. -k bilan bitta valyutani tanlab test qiling

Vazifa 5: Parametrlashtirilgan fixture

  1. params=["memory", "fayl"] bilan baza fixture
  2. Bir test to'plami ikkala bazada ishlashini tekshiring
  3. Har baza uchun setup/teardown (yield)
  4. Qaysi konfiguratsiyada yiqilganini id bilan ko'rsating

Vazifa 6: Jadvalni CSV dan o'qish

  1. Sinov holatlarini CSV 16.3-bob faylida saqlang
  2. parametrize uchun ularni o'qib ro'yxatga aylantiring
  3. Har qatorga id bering (CSV ustunidan)
  4. Yangi holat qo'shish faqat CSV ni tahrirlash bilan bo'lsin

Vazifa 7: O'ylash

parametrize sinov holatlarini kod dan ma'lumot ga aylantiradi. Bu "data-driven testing" (ma'lumotga asoslangan testlash) deb ataladi va u dasturlashda keng tarqalgan g'oya — "kodni ma'lumot bilan almashtirish". Nima uchun sinov holatlarini ma'lumot sifatida ifodalash afzal, va bu g'oya (kod → ma'lumot) dasturlashning boshqa qaysi joylarida uchraydi?

Javob

Qisqa javob: Sinov holatlarini ma'lumot sifatida ifodalash afzal, chunki ma'lumotni o'zgartirish koddan osonroq va xavfsizroq: yangi holat qo'shish bitta qator, mantiq esa o'zgarmaydi. Bu "kod → ma'lumot" g'oyasi dasturlashning keng tamoyili: takrorlanuvchi mantiqni bir marta yozib, o'zgaruvchan qismni ma'lumot sifatida ajratish. U konfiguratsiya, jadval, qoidalar dvigateli, deklarativ dasturlash — hamma joyda uchraydi.

1. Nega ma'lumot afzal

Kod sifatida Ma'lumot sifatida
Har holat — funksiya Har holat — jadval qatori
Takror mantiq Bir marta mantiq
Yangi holat — kod yozish Yangi holat — qator
O'qish qiyin Jadval — o'qiladigan
Xato ehtimoli yuqori Ma'lumot xavfsizroq

2. "Kod → ma'lumot" boshqa joylarda

Joy Kod o'rniga ma'lumot
Konfiguratsiya Qattiq kodlash o'rniga TOML/YAML (16.6)
Marshrutlash (routing) if/elif o'rniga marshrut jadvali
Qoidalar dvigateli Kod o'rniga qoidalar ma'lumoti
Holatlar mashinasi O'tishlar jadvali
SQL Sikl o'rniga deklarativ so'rov
Regex Tahlil kodi o'rniga naqsh
Ma'lumotga asoslangan test parametrize

3. Nega bu tamoyil kuchli

Sabab Tafsilot
Ma'lumot o'zgaruvchan Kodni buzmasdan kengaytirish
Ma'lumot tekshiriladi Sxema, validatsiya
Ma'lumot ko'chma Fayl, baza, tarmoq
Ma'lumot dasturchisiz o'zgaradi Biznes foydalanuvchi ham
Mantiq bir joyda Test, sozlash

4. Chegaralari

Chegara Izoh
Murakkab mantiq ma'lumotga sig'maydi Ba'zan kod aniqroq
Haddan ziyod umumlashtirish "Hamma narsa ma'lumot" — o'qib bo'lmaydi
Nosozlikni tuzatish qiyinroq Ma'lumot xatosi kod xatosidan yashirinroq

5. Muhandislik saboqlari

  1. Takrorlanuvchi mantiqni ma'lumot bilan ajrating
  2. O'zgaruvchan qism — ma'lumot, barqaror qism — kod
  3. Ma'lumot kengaytirish va o'qishni osonlashtiradi
  4. Lekin murakkab mantiq baribir kodda aniqroq

6. Xulosa

  1. Sinov holatlari ma'lumot sifatida — oson kengaytiriladi
  2. "Kod → ma'lumot" — keng dasturlash tamoyili
  3. Konfiguratsiya, routing, qoidalar — hammasi shu g'oya
  4. Muvozanat: o'zgaruvchan — ma'lumot, murakkab mantiq — kod

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda parametrize bilan bir testni ko'p ma'lumot bilan ishga tushirishni o'rgandik.

Eng muhim uch fikr:

  1. parametrize sinov holatlarini kod emas, ma'lumot qiladi. @pytest.mark.parametrize("kirish, kutilgan", [...]) bitta test funksiyasidan har qator uchun alohida test yaratadi. Bu for sikl ichida assert dan yaxshiroq: sikl birinchi yiqilishda to'xtaydi va qaysi holat ekani noaniq, parametrize esa har holatni mustaqil ishlatadi va yiqilgan holatning aynan kirishini ko'rsatadi.

  2. pytest.param — id va belgilar. Har holatga id= bilan o'qiladigan nom (test_x[ildiz-4]) va marks= bilan belgi (xfail, skip) berish mumkin — ishlamaydigan holatni oddiy qiymat bilan emas, marks=pytest.mark.xfail bilan belgilang. Ikki parametrize ni ustma-ust qo'yish barcha kombinatsiyani beradi, lekin ular tez o'sadi — faqat mantiqan zarur bo'lganda.

  3. Fixture bilan birga va parametrlashtirilgan fixture. parametrize fixture bilan birga ishlaydi (ikkalasi parametr sifatida qo'shiladi), va fixture ning o'zini params= bilan parametrlash bir testni turli konfiguratsiyalarda (masalan turli bazalarda) takrorlaydi. Chegara holatlarini toza jadval bilan qamrang — sinov holatlari kodning kutilgan xulqini hujjatlaydigan jadvalga aylanadi.

Keyingi darsda mock tushunchasini o'rganamiz: tashqi bog'liqliklarni (tarmoq, vaqt, tasodif, baza) soxta obyektlar bilan almashtirib, kodni izolyatsiyada, tez va takrorlanuvchi test qilish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.4-dars: parametrize — IlmHamroh