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.

🕑 14 daqiqa o‘qish 📄 809 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo
  2. Yechim: parametrlash
  3. Chegara qiymatlari
  4. Holatlarga nom berish
  5. Xato holatlarini ham parametrlash
  6. Parametrlarni ko'paytirish
  7. Qachon parametrlamaslik kerak
  8. Xulosa

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:

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

Python
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 #

Python
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
Terminal
pytest -v test_parametr.py
Natija
========================= 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 sikliparametrize
Nechta test ko'rinadiBittaHar holat uchun bitta
Birinchisi yiqilsaQolganlari bajarilmaydiQolganlari baribir ishlaydi
HisobotdaFaqat "yiqildi"Qaysi qiymatda yiqilgani
Bo'sh ro'yxat bo'lsaJimgina o'tadipytest ogohlantiradi
Bittasini ishga tushirishImkonsiz-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:

Python
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
Terminal
pytest -q test_chegara.py
Natija
..........                                                       [100%]
10 passed in 0.01s
Xato chegarada yashiringan bo'ladi F: 0-59 D: 60-69 C: 70-79 B: 80-89 A: 90-100 Uzuq chiziqlar - xato eng ehtimol joylar Har chegara uchun uchta qiymatni tekshiring: chegaraning o'zi, bir past va bir yuqori 90 dan katta va 90 dan katta yoki teng - bir belgilik farq, butun bahoni o'zgartiradi
>= ni > deb yozish - eng ko'p uchraydigan xato

Chegara testi haqiqatan ish beradimi? Funksiyada >= ni > ga almashtiramiz - bu bir belgilik xato:

Python
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"
Terminal
pytest -q --tb=line test_takror.py test_chegara.py
Natija
......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:

Python
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"
Chegara qiymatlari ro'yxati

Qaysi turdagi ma'lumot bo'lsa ham, shu ro'yxatdan boshlang:

TurSinash kerak bo'lgan qiymatlar
Son0, -1, 1, eng kichik, eng katta, chegaradan bir past/yuqori
Ro'yxatBo'sh, bitta element, ikkita, juda katta
SatrBo'sh, bitta belgi, bo'shliq, uzun, emoji
SanaOyning oxirgi kuni, 29-fevral, yil almashishi
Lug'atBo'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:

Python
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
Terminal
pytest -v test_id.py
Natija
========================= 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:

Python
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)
Terminal
pytest -q test_xato.py
Natija
....                                                             [100%]
4 passed in 0.01s

Parametrlarni ko'paytirish #

Ikkita parametrize bir-birining ustiga qo'yilsa, pytest ularning barcha juftliklarini hosil qiladi:

Python
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 != ""
Terminal
pytest -q test_kopaytirish.py
Natija
......                                                           [100%]
6 passed in 0.01s

Ikkita ro'yxat (2 ta til va 3 ta harf) oltita testga aylandi.

Ko'paytirish tez o'sib ketadi

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.

HolatParametrlash
Bir xil chaqiruv, boshqa qiymatlarHa
Bir xil shakldagi xato holatlariHa
Har holat uchun boshqa tayyorgarlikYo'q - alohida test
Har holat uchun boshqa tekshiruvYo'q - alohida test
Qiymatlar ro'yxati kodda hisoblanadiYo'q - qo'lda yozing
Amaliy topshiriq
  1. baho.py ni yozib, beshta takrorlangan testni parametrli testga aylantiring.
  2. pytest -v chiqishidagi nomlarni o'qing va qavs ichida nima borligini tushuntiring.
  3. Chegara testini yozing va uni >= dan > ga o'tkazib sinab ko'ring.
  4. Nechta holat yiqilganini sanang va nima uchun aynan shuncha ekanini ayting.
  5. O'rtadagi qiymatlar testi nima uchun xatoni sezmaganini yozing.
  6. ids bilan to'rtta holatga nom bering.
  7. -k bayrog'i bilan faqat bitta holatni ishga tushiring.
  8. Noto'g'ri ballar ro'yxatiga None qo'shing - qanday xato chiqadi?
  9. Ikkita parametrni ko'paytirib, nechta test hosil bo'lishini oldindan ayting.
  10. O'z loyihangizdagi takrorlanuvchi uchta testni bitta parametrli testga jamlang.

Xulosa #

  • @pytest.mark.parametrize bitta 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.raises ni test ichiga qo'yib.
  • Ikkita parametrize ustma-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.

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.