6-bo‘lim
Parametrli testlar
Bitta test bilan o'nlab holatni tekshirish, chegara qiymatlari, id berish, parametrlarni ko'paytirish va xato holatlarini ham parametrga chiqarish.
Ushbu bo‘lim mundarijasi
Oldingi bo'limda testdagi for sikli bo'sh ro'yxat ustida aylanib, hech
narsa tekshirmasdan o'tib ketganini ko'rdik.
pytest da bu muammoning tayyor yechimi bor: parametrlash.
Muammo #
Ballni harfli bahoga aylantiradigan funksiya yozamiz:
def baho_harfi(ball):
if not 0 <= ball <= 100:
raise ValueError(f"ball 0 va 100 orasida bo'lishi kerak, berildi: {ball}")
if ball >= 90:
return "A"
if ball >= 80:
return "B"
if ball >= 70:
return "C"
if ball >= 60:
return "D"
return "F"
Uni tekshirish uchun bir xil shakldagi testlarni ko'chirib yozish kerak bo'ladi:
from baho import baho_harfi
def test_95_ball_a_beradi():
assert baho_harfi(95) == "A"
def test_85_ball_b_beradi():
assert baho_harfi(85) == "B"
def test_75_ball_c_beradi():
assert baho_harfi(75) == "C"
def test_65_ball_d_beradi():
assert baho_harfi(65) == "D"
def test_30_ball_f_beradi():
assert baho_harfi(30) == "F"
Beshta test, beshta bir xil qator. Yangi holat qo'shish uchun yana ko'chirish kerak.
Yechim: parametrlash #
import pytest
from baho import baho_harfi
@pytest.mark.parametrize("ball, kutilgan", [
(95, "A"),
(85, "B"),
(75, "C"),
(65, "D"),
(30, "F"),
])
def test_ball_harfga_aylanadi(ball, kutilgan):
assert baho_harfi(ball) == kutilgan
pytest -v test_parametr.py
========================= test session starts ==========================
platform linux -- Python 3.13.12, pytest-9.1.1, pluggy-1.6.0 -- /home/dasturchi/loyiha/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/dasturchi/loyiha
collecting ... collected 5 items
test_parametr.py::test_ball_harfga_aylanadi[95-A] PASSED [ 20%]
test_parametr.py::test_ball_harfga_aylanadi[85-B] PASSED [ 40%]
test_parametr.py::test_ball_harfga_aylanadi[75-C] PASSED [ 60%]
test_parametr.py::test_ball_harfga_aylanadi[65-D] PASSED [ 80%]
test_parametr.py::test_ball_harfga_aylanadi[30-F] PASSED [100%]
========================== 5 passed in 0.01s ===========================
Bitta funksiya yozdik, pytest esa beshta alohida test yaratdi. Bu sikldan tubdan farq qiladi:
for sikli | parametrize | |
|---|---|---|
| Nechta test ko'rinadi | Bitta | Har holat uchun bitta |
| Birinchisi yiqilsa | Qolganlari bajarilmaydi | Qolganlari baribir ishlaydi |
| Hisobotda | Faqat "yiqildi" | Qaysi qiymatda yiqilgani |
| Bo'sh ro'yxat bo'lsa | Jimgina o'tadi | pytest ogohlantiradi |
| Bittasini ishga tushirish | Imkonsiz | -k "95" bilan mumkin |
Qavs ichidagi qiymatlar test nomiga qo'shilib ketganiga e'tibor bering:
test_ball_harfga_aylanadi[95-A]. Shuning uchun yiqilganda qaysi holat
singanini darhol ko'rasiz.
Chegara qiymatlari #
Endi eng muhim joyga o'tamiz. Yuqoridagi beshta qiymat - o'rtadagi qiymatlar. Xato esa deyarli har doim chegarada yashiringan bo'ladi:
import pytest
from baho import baho_harfi
@pytest.mark.parametrize("ball, kutilgan", [
(100, "A"), # eng yuqori
(90, "A"), # A ning pastki chegarasi
(89, "B"), # bir ball pastda
(80, "B"),
(79, "C"),
(70, "C"),
(69, "D"),
(60, "D"),
(59, "F"),
(0, "F"), # eng past
])
def test_chegaradagi_ballar(ball, kutilgan):
assert baho_harfi(ball) == kutilgan
pytest -q test_chegara.py
.......... [100%]
10 passed in 0.01s
Chegara testi haqiqatan ish beradimi? Funksiyada >= ni > ga
almashtiramiz - bu bir belgilik xato:
def baho_harfi(ball):
if not 0 <= ball <= 100:
raise ValueError(f"ball 0 va 100 orasida bo'lishi kerak, berildi: {ball}")
if ball > 90:
return "A"
if ball > 80:
return "B"
if ball > 70:
return "C"
if ball > 60:
return "D"
return "F"
pytest -q --tb=line test_takror.py test_chegara.py
......F.F.F.F.. [100%]
=============================== FAILURES ===============================
E AssertionError: assert 'B' == 'A'
- A
+ B
/home/dasturchi/loyiha/test_chegara.py:19: AssertionError: assert 'B' == 'A'
E AssertionError: assert 'C' == 'B'
- B
+ C
/home/dasturchi/loyiha/test_chegara.py:19: AssertionError: assert 'C' == 'B'
E AssertionError: assert 'D' == 'C'
- C
+ D
/home/dasturchi/loyiha/test_chegara.py:19: AssertionError: assert 'D' == 'C'
E AssertionError: assert 'F' == 'D'
- D
+ F
/home/dasturchi/loyiha/test_chegara.py:19: AssertionError: assert 'F' == 'D'
======================= short test summary info ========================
FAILED test_chegara.py::test_chegaradagi_ballar[90-A] - AssertionErro...
FAILED test_chegara.py::test_chegaradagi_ballar[80-B] - AssertionErro...
FAILED test_chegara.py::test_chegaradagi_ballar[70-C] - AssertionErro...
FAILED test_chegara.py::test_chegaradagi_ballar[60-D] - AssertionErro...
4 failed, 11 passed in 0.02s
Birinchi fayl - o'rtadagi qiymatlar bilan - xatoni umuman sezmadi. Chegara testi esa to'rtta holatda yiqildi va har birida aniq qiymat ko'rsatilgan.
Kodni qaytaramiz:
def baho_harfi(ball):
if not 0 <= ball <= 100:
raise ValueError(f"ball 0 va 100 orasida bo'lishi kerak, berildi: {ball}")
if ball >= 90:
return "A"
if ball >= 80:
return "B"
if ball >= 70:
return "C"
if ball >= 60:
return "D"
return "F"
Qaysi turdagi ma'lumot bo'lsa ham, shu ro'yxatdan boshlang:
| Tur | Sinash kerak bo'lgan qiymatlar |
|---|---|
| Son | 0, -1, 1, eng kichik, eng katta, chegaradan bir past/yuqori |
| Ro'yxat | Bo'sh, bitta element, ikkita, juda katta |
| Satr | Bo'sh, bitta belgi, bo'shliq, uzun, emoji |
| Sana | Oyning oxirgi kuni, 29-fevral, yil almashishi |
| Lug'at | Bo'sh, kalit yo'q, qiymat None |
Tajriba shuni ko'rsatadi: xatolarning katta qismi aynan shu qiymatlarda topiladi.
Holatlarga nom berish #
Sonlar uchun avtomatik nomlar tushunarli, lekin murakkab qiymatlar
uchun ular o'qilmaydi. ids yordam beradi:
import pytest
from baho import baho_harfi
@pytest.mark.parametrize("ball, kutilgan", [
(90, "A"),
(89, "B"),
(60, "D"),
(59, "F"),
], ids=[
"a_ning_pastki_chegarasi",
"chegaradan_bir_ball_past",
"d_ning_pastki_chegarasi",
"otish_chegarasidan_past",
])
def test_nomlangan_chegaralar(ball, kutilgan):
assert baho_harfi(ball) == kutilgan
pytest -v test_id.py
========================= test session starts ==========================
platform linux -- Python 3.13.12, pytest-9.1.1, pluggy-1.6.0 -- /home/dasturchi/loyiha/.venv/bin/python3
cachedir: .pytest_cache
rootdir: /home/dasturchi/loyiha
collecting ... collected 4 items
test_id.py::test_nomlangan_chegaralar[a_ning_pastki_chegarasi] PASSED [ 25%]
test_id.py::test_nomlangan_chegaralar[chegaradan_bir_ball_past] PASSED [ 50%]
test_id.py::test_nomlangan_chegaralar[d_ning_pastki_chegarasi] PASSED [ 75%]
test_id.py::test_nomlangan_chegaralar[otish_chegarasidan_past] PASSED [100%]
========================== 4 passed in 0.01s ===========================
Endi hisobot o'qiladigan bo'ldi - har qator nima tekshirilayotganini aytib turibdi.
Xato holatlarini ham parametrlash #
Noto'g'ri kirishlar ham bir xil shaklda bo'lgani uchun ularni ham bitta testga jamlash mumkin:
import pytest
from baho import baho_harfi
@pytest.mark.parametrize("notogri_ball", [-1, 101, 1000, -50])
def test_chegaradan_tashqari_ball_xato_beradi(notogri_ball):
with pytest.raises(ValueError, match="0 va 100"):
baho_harfi(notogri_ball)
pytest -q test_xato.py
.... [100%]
4 passed in 0.01s
Parametrlarni ko'paytirish #
Ikkita parametrize bir-birining ustiga qo'yilsa, pytest ularning
barcha juftliklarini hosil qiladi:
import pytest
def format_baho(harf, til):
matnlar = {
"uz": {"A": "a'lo", "B": "yaxshi", "F": "qoniqarsiz"},
"en": {"A": "excellent", "B": "good", "F": "fail"},
}
return matnlar[til][harf]
@pytest.mark.parametrize("til", ["uz", "en"])
@pytest.mark.parametrize("harf", ["A", "B", "F"])
def test_har_harf_har_tilda_matn_beradi(harf, til):
natija = format_baho(harf, til)
assert isinstance(natija, str)
assert natija != ""
pytest -q test_kopaytirish.py
...... [100%]
6 passed in 0.01s
Ikkita ro'yxat (2 ta til va 3 ta harf) oltita testga aylandi.
Uchta parametr - har birida 5 tadan qiymat - 125 ta test beradi. To'rtta bo'lsa 625 ta.
Testlar tez bo'lgani uchun buni sezmaysiz, lekin ularning katta qismi bir xil kod yo'lini takrorlaydi va yangi ma'lumot bermaydi.
Qoida: har bir parametr mustaqil o'zgarishni ifodalasin. Agar til va harf bir-biriga ta'sir qilmasa, ularni ko'paytirishning hojati yo'q - ikkita alohida parametrlangan test yetadi.
Qachon parametrlamaslik kerak #
Parametrlash faqat tayyorlash va tekshirish bir xil bo'lganda
ishlaydi. Agar har holat uchun boshqacha tayyorgarlik kerak bo'lsa,
parametrlar ichiga if kirib keladi - bu esa 5-bo'limdagi qoidani
buzadi.
| Holat | Parametrlash |
|---|---|
| Bir xil chaqiruv, boshqa qiymatlar | Ha |
| Bir xil shakldagi xato holatlari | Ha |
| Har holat uchun boshqa tayyorgarlik | Yo'q - alohida test |
| Har holat uchun boshqa tekshiruv | Yo'q - alohida test |
| Qiymatlar ro'yxati kodda hisoblanadi | Yo'q - qo'lda yozing |
baho.pyni yozib, beshta takrorlangan testni parametrli testga aylantiring.pytest -vchiqishidagi nomlarni o'qing va qavs ichida nima borligini tushuntiring.- Chegara testini yozing va uni
>=dan>ga o'tkazib sinab ko'ring. - Nechta holat yiqilganini sanang va nima uchun aynan shuncha ekanini ayting.
- O'rtadagi qiymatlar testi nima uchun xatoni sezmaganini yozing.
idsbilan to'rtta holatga nom bering.-kbayrog'i bilan faqat bitta holatni ishga tushiring.- Noto'g'ri ballar ro'yxatiga
Noneqo'shing - qanday xato chiqadi? - Ikkita parametrni ko'paytirib, nechta test hosil bo'lishini oldindan ayting.
- O'z loyihangizdagi takrorlanuvchi uchta testni bitta parametrli testga jamlang.
Xulosa #
@pytest.mark.parametrizebitta test funksiyasidan bir nechta alohida test yasaydi.- Sikldan farqi: har holat mustaqil ishlaydi va biri yiqilsa, qolganlari baribir bajariladi.
- Qiymatlar test nomiga qo'shiladi:
test_nom[95-A]- yiqilganda qaysi holat singanini darhol ko'rasiz. - Xato deyarli har doim chegarada yashiringan bo'ladi, o'rtadagi qiymatlarda emas.
- Har chegara uchun uchta qiymatni sinang: chegaraning o'zi, bir past va bir yuqori.
>=ni>deb yozish - eng ko'p uchraydigan xato; o'rtadagi qiymatlar testi uni umuman sezmaydi.ids=[...]har holatga o'qiladigan nom beradi - murakkab qiymatlar uchun shart.- Xato holatlarini ham parametrlash mumkin:
pytest.raisesni test ichiga qo'yib. - Ikkita
parametrizeustma-ust qo'yilsa, pytest ularning barcha juftliklarini hosil qiladi. - Ko'paytirish tez o'sadi: uchta parametrda 5 tadan qiymat - 125 ta test.
- Parametrlash tayyorgarlik va tekshiruv bir xil bo'lgandagina to'g'ri keladi.
- Har holat uchun boshqa tayyorgarlik kerak bo'lsa - alohida testlar yozing.
Keyingi bo'limda tayyorgarlikning o'ziga o'tamiz: fixture bilan takrorlanadigan sozlashni bir joyga yig'amiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.