Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Asosiy parametrize
- 2.2. Har holat — alohida test
- 2.3. pytest.param — id va belgilar
- 2.4. Ko'p parametr (stacking)
- 2.5. parametrize va fixture birga
- 2.6. Parametrlashtirilgan fixture
- 2.7. Chegara holatlari jadvali
- 2.8. Qachon parametrize, qachon alohida test
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Asosiy parametrize
- Misol 2 — pytest.param: id, belgilar va aniq yiqilish
- Misol 3 — Stacking, fixture va parametrlashtirilgan fixture
- Misol 4 — Amaliy: soliq va validatsiya jadvallari
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
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)
parametrizeva fixture birga- Parametrlashtirilgan fixture (
params=) - Chegara holatlarini jadval bilan qamrash
- Amaliy: soliq va validatsiya jadvallari
2. Nazariya — chuqur tushuntirish
2.1. Asosiy parametrize
import pytest
@pytest.mark.parametrize("kirish, kutilgan", [
(2, 4),
(3, 9),
(0, 0),
(-2, 4),
])
def test_kvadrat(kirish, kutilgan):
assert kirish ** 2 == kutilganBu 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:
@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:
@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:
@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 * 102.6. Parametrlashtirilgan fixture
Fixture ning o'zini parametrlash mumkin — u har qiymat uchun bir marta ishlaydi, va uni ishlatgan barcha test har qiymatda takrorlanadi:
@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:
@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) == yaroqli2.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
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.paramQoidalar
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 bilan4. Batafsil misollar
Misollarda
pytestni deterministik ishga tushiruvchipytest_ishgayordamchisi ishlatiladi.
Misol 1 — Asosiy parametrize
"""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:
=== 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 yaratildiNima ko'rsatdi: 2.1, 2.2, 2.7-bo'limlar.
Misol 2 — pytest.param: id, belgilar va aniq yiqilish
"""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:
=== 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'rinadiNima ko'rsatdi: 2.2, 2.3-bo'limlar.
Misol 3 — Stacking, fixture va parametrlashtirilgan fixture
"""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:
=== 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: 0Nima 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.
"""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:
=== 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 qatorNima 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
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 == y2. for sikl ichida assert
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 == y3. Parametr nomlari mos kelmasa
@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
@pytest.mark.parametrize("x", [(1,), (2,)]) # ❌ ortiqcha kortej
@pytest.mark.parametrize("x", [1, 2]) # ✅ bitta parametr — oddiy qiymat5. Kombinatsiyalarni haddan oshirish
@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 kombinatsiyalar6. id siz o'qib bo'lmaydigan nomlar
@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
@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 —
parametrizebilan 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
Bir xil mantiq, farqli ma'lumot —
parametrizejadvali.forsikl +asserto'rnigaparametrize(aniq yiqilish).Har holatga o'qiladigan
id=bering.Ishlamaydigan/xato holatlarni
marks=bilan belgilang.Chegara holatlarini jadval bilan to'liq qamrang.
Kombinatsiyalarni (stacking) faqat mantiqan zarur bo'lganda.
Turli konfiguratsiyalar uchun
params=fixture.Jadvalni o'qiladigan tuting — sinov holatlari hujjat vazifasini ham bajaradi.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
Har parametrize nechta test yaratishini ayting:
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
326(2 × 3)0— bo'sh ro'yxat, hech qanday test1 test funksiyasi × 2 param = 22 test yaratiladi, 1 ishlaydi (1 skip)8(2 × 2 × 2)2 PASSED
Vazifa 2: Xatolarni tuzating
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'lmaydiJavoblar
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:
- Yaroqli emaillar (oddiy, nuqta, pastki chiziq, raqam)
- Yaroqsiz (@ yo'q, domen yo'q, bo'shliq, bo'sh)
- Har holatga o'qiladigan
id pytest -vda har biri alohida ko'rinsin
Vazifa 4: Kombinatsiya testlari
To'lov tizimi (valyuta × usul × summa):
parametrizestacking bilan kombinatsiyalar- Ba'zi kombinatsiyalar mumkin emas —
marks=xfail - Kombinatsiyalar sonini hisoblang va oqilona chegaralang
-kbilan bitta valyutani tanlab test qiling
Vazifa 5: Parametrlashtirilgan fixture
params=["memory", "fayl"]bilan baza fixture- Bir test to'plami ikkala bazada ishlashini tekshiring
- Har baza uchun setup/teardown (
yield) - Qaysi konfiguratsiyada yiqilganini
idbilan ko'rsating
Vazifa 6: Jadvalni CSV dan o'qish
- Sinov holatlarini CSV 16.3-bob faylida saqlang
parametrizeuchun ularni o'qib ro'yxatga aylantiring- Har qatorga
idbering (CSV ustunidan) - 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
- Takrorlanuvchi mantiqni ma'lumot bilan ajrating
- O'zgaruvchan qism — ma'lumot, barqaror qism — kod
- Ma'lumot kengaytirish va o'qishni osonlashtiradi
- Lekin murakkab mantiq baribir kodda aniqroq
6. Xulosa
- Sinov holatlari ma'lumot sifatida — oson kengaytiriladi
- "Kod → ma'lumot" — keng dasturlash tamoyili
- Konfiguratsiya, routing, qoidalar — hammasi shu g'oya
- 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:
parametrizesinov holatlarini kod emas, ma'lumot qiladi.@pytest.mark.parametrize("kirish, kutilgan", [...])bitta test funksiyasidan har qator uchun alohida test yaratadi. Buforsikl ichidaassertdan yaxshiroq: sikl birinchi yiqilishda to'xtaydi va qaysi holat ekani noaniq,parametrizeesa har holatni mustaqil ishlatadi va yiqilgan holatning aynan kirishini ko'rsatadi.pytest.param— id va belgilar. Har holatgaid=bilan o'qiladigan nom (test_x[ildiz-4]) vamarks=bilan belgi (xfail,skip) berish mumkin — ishlamaydigan holatni oddiy qiymat bilan emas,marks=pytest.mark.xfailbilan belgilang. Ikkiparametrizeni ustma-ust qo'yish barcha kombinatsiyani beradi, lekin ular tez o'sadi — faqat mantiqan zarur bo'lganda.Fixture bilan birga va parametrlashtirilgan fixture.
parametrizefixture bilan birga ishlaydi (ikkalasi parametr sifatida qo'shiladi), va fixture ning o'ziniparams=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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!