3-bo‘lim

Assert, istisno va xato xabarini o'qish

Turli qiymatlarni solishtirish, kasr sonlar bilan tuzoq, pytest.approx, istisnolarni tekshirish va foydali xato xabarlari yozish.

🕑 17 daqiqa o‘qish 📄 786 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Oddiy solishtirishlar
  2. Xato xabari turga qarab o'zgaradi
  3. Kasr sonlar tuzog'i
  4. Istisnolarni tekshirish
  5. Xato xabarini o'zingiz yozish
  6. Xulosa

Test - bu assert. Boshqa hamma narsa (fixture, dublyor, parametr) - shu bitta amal atrofidagi qulayliklar.

Shuning uchun bu bo'limni to'liq assert ga bag'ishlaymiz.

Oddiy solishtirishlar #

pytest da maxsus tasdiqlash metodlari yo'q - Python ning o'z operatorlari ishlatiladi:

Python
def test_sonlar():
    assert 2 + 2 == 4
    assert 10 > 3
    assert 7 != 8


def test_satrlar():
    assert "salom".upper() == "SALOM"
    assert "dastur" in "dasturlash"
    assert "abc".startswith("a")


def test_royxatlar():
    assert [1, 2, 3] == [1, 2, 3]
    assert len([1, 2, 3]) == 3
    assert 2 in [1, 2, 3]


def test_lugatlar():
    talaba = {"ism": "Aziza", "baho": 90}
    assert talaba["baho"] == 90
    assert "ism" in talaba
Terminal
pytest -q test_solishtirish.py
Natija
....                                                             [100%]
4 passed in 0.01s

Har bir tur uchun alohida metod yo'q - ==, in, > yetarli.

Xato xabari turga qarab o'zgaradi #

pytest solishtirilayotgan narsaning turini biladi va farqni o'sha turga mos ko'rinishda ko'rsatadi. Ro'yxatlarda:

Python
def test_royxat_farqi():
    kutilgan = ["olma", "anor", "uzum", "shaftoli"]
    olingan = ["olma", "anor", "nok", "shaftoli"]
    assert olingan == kutilgan
Terminal
pytest -q test_farq.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
__________________________ test_royxat_farqi ___________________________

    def test_royxat_farqi():
        kutilgan = ["olma", "anor", "uzum", "shaftoli"]
        olingan = ["olma", "anor", "nok", "shaftoli"]
>       assert olingan == kutilgan
E       AssertionError: assert ['olma', 'ano...', 'shaftoli'] == ['olma', 'ano...', 'shaftoli']
E         
E         At index 2 diff: 'nok' != 'uzum'
E         Use -v to get more diff

test_farq.py:4: AssertionError
======================= short test summary info ========================
FAILED test_farq.py::test_royxat_farqi - AssertionError: assert ['olm...
1 failed in 0.01s

pytest qaysi indeksda farq borligini o'zi topdi. Lug'atlar bilan ham shunday:

Python
def test_lugat_farqi():
    kutilgan = {"ism": "Aziza", "baho": 90, "guruh": "A"}
    olingan = {"ism": "Aziza", "baho": 85, "guruh": "A"}
    assert olingan == kutilgan
Terminal
pytest -q test_farq.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
___________________________ test_lugat_farqi ___________________________

    def test_lugat_farqi():
        kutilgan = {"ism": "Aziza", "baho": 90, "guruh": "A"}
        olingan = {"ism": "Aziza", "baho": 85, "guruh": "A"}
>       assert olingan == kutilgan
E       AssertionError: assert {'ism': 'Aziz... 'guruh': 'A'} == {'ism': 'Aziz... 'guruh': 'A'}
E         
E         Omitting 2 identical items, use -vv to show
E         Differing items:
E         {'baho': 85} != {'baho': 90}
E         Use -v to get more diff

test_farq.py:4: AssertionError
======================= short test summary info ========================
FAILED test_farq.py::test_lugat_farqi - AssertionError: assert {'ism'...
1 failed in 0.01s

Uchta kalitdan faqat farq qilgani ko'rsatildi. Katta obyektlarda bu juda qo'l keladi.

Kasr sonlar tuzog'i #

Endi eng ko'p uchraydigan xatoga o'tamiz:

Python
def test_oddiy_qoshish():
    assert 0.1 + 0.2 == 0.3
Terminal
pytest -q test_kasr.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
__________________________ test_oddiy_qoshish __________________________

    def test_oddiy_qoshish():
>       assert 0.1 + 0.2 == 0.3
E       assert (0.1 + 0.2) == 0.3

test_kasr.py:2: AssertionError
======================= short test summary info ========================
FAILED test_kasr.py::test_oddiy_qoshish - assert (0.1 + 0.2) == 0.3
1 failed in 0.01s

Test yiqildi - lekin kodda xato yo'q. E'tibor bering, pytest bu safar qiymatlarni ochib bermadi: u faqat (0.1 + 0.2) == 0.3 deb yozdi. Sababini ko'rish uchun yig'indini o'zimiz chop etamiz:

Terminal
python3 -c "print(0.1 + 0.2)"
Natija
0.30000000000000004

Muammo kompyuterning kasr sonlarni saqlash usulida: 0.1 va 0.2 ikkilik sanoqda aniq ifodalanmaydi, shuning uchun ularning yig'indisi 0.3 dan mikroskopik farq qiladi.

Kasr sonlarni hech qachon == bilan solishtirmang

Bu qoida testlarga emas, umuman barcha dasturlarga tegishli. Pul, o'lchov, foiz - qayerda kasr son bo'lsa, tenglik tekshiruvi ertami-kechmi yiqiladi.

Testlarda yechim - pytest.approx. Oddiy kodda esa butun songa o'tish (pulni tiyinda saqlash) yoki decimal.Decimal ishlatish.

Yechim - pytest.approx:

Python
import pytest


def test_approx_bilan():
    assert 0.1 + 0.2 == pytest.approx(0.3)


def test_royxat_uchun_ham():
    assert [0.1 + 0.2, 0.2 + 0.4] == pytest.approx([0.3, 0.6])


def test_aniqlikni_ozi_belgilash():
    # 1% gacha farqqa ruxsat
    assert 100.4 == pytest.approx(100, rel=0.01)

    # mutlaq farq 0.5 dan oshmasin
    assert 100.4 == pytest.approx(100, abs=0.5)
Terminal
pytest -q test_kasr.py
Natija
...                                                              [100%]
3 passed in 0.01s
YozuvNimani tekshiradi
approx(0.3)Sukut bo'yicha ~0.000001 farqqa ruxsat
approx(100, rel=0.01)Nisbiy farq: 1% gacha
approx(100, abs=0.5)Mutlaq farq: 0.5 birlikkacha
approx([1.0, 2.0])Ro'yxat ichidagi har bir sonni alohida
approx({"a": 1.0})Lug'at qiymatlarini

Istisnolarni tekshirish #

Funksiya to'g'ri ishlashini tekshirish - ishning yarmi. Ikkinchi yarmi - u noto'g'ri kirishda to'g'ri xato berishini tekshirish.

Avval funksiyani yozamiz:

Python
def ortacha(baholar):
    if not baholar:
        raise ValueError("baholar ro'yxati bo'sh")
    return sum(baholar) / len(baholar)

Endi istisnoni pytest.raises bilan ushlaymiz:

Python
import pytest

from hisob import ortacha


def test_bosh_royxat_xato_beradi():
    with pytest.raises(ValueError):
        ortacha([])


def test_xato_matni_ham_tekshiriladi():
    with pytest.raises(ValueError, match="bo'sh"):
        ortacha([])


def test_xato_obyektini_korish():
    with pytest.raises(ValueError) as xato:
        ortacha([])
    assert "ro'yxati" in str(xato.value)
Terminal
pytest -q test_istisno.py
Natija
...                                                              [100%]
3 passed in 0.01s

Uchta usul ham to'g'ri. match= - eng qulayi: u ham istisno turini, ham xabar matnini bitta qatorda tekshiradi.

pytest.raises ni bo'sh tanaga yozmang

Quyidagi test doim o'tadi va hech narsani tekshirmaydi:

Python
with pytest.raises(ValueError):
    ortacha([])
    assert False   # bu qatorga hech qachon yetib kelinmaydi

Birinchi qator istisno tashlagani uchun keyingi qatorlar bajarilmaydi. Shuning uchun with blokiga faqat bitta chaqiruv yozing, qolgan tekshiruvlarni blokdan keyin qiling.

Yana bir xavf: match= argumenti doimiy ifoda (regex) sifatida o'qiladi. match="narx (so'm)" yozsangiz, qavslar guruh sifatida tushuniladi. Maxsus belgilarni re.escape() bilan himoyalang.

Istisno tashlanmasa nima bo'ladi? Testni ataylab buzib ko'ramiz:

Python
import pytest

from hisob import ortacha


def test_notogri_kutish():
    with pytest.raises(ValueError):
        ortacha([70, 80])
Terminal
pytest -q test_istisno.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
_________________________ test_notogri_kutish __________________________

    def test_notogri_kutish():
>       with pytest.raises(ValueError):
             ^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

test_istisno.py:7: Failed
======================= short test summary info ========================
FAILED test_istisno.py::test_notogri_kutish - Failed: DID NOT RAISE V...
1 failed in 0.01s

DID NOT RAISE - "istisno tashlanmadi" degani. Bu ham foydali xato: demak kod siz kutgan holatda xato bermayapti.

Xato xabarini o'zingiz yozish #

Ba'zan assert ning avtomatik xabari yetarli bo'lmaydi. Vergul qo'yib, o'z izohingizni qo'shish mumkin:

Python
from hisob import ortacha


def test_izoh_bilan():
    baholar = [45, 50, 55]
    natija = ortacha(baholar)
    assert natija >= 60, f"guruh o'rtachasi past: {natija:.1f} ({baholar})"
Terminal
pytest -q test_izoh.py
Natija
F                                                                [100%]
=============================== FAILURES ===============================
___________________________ test_izoh_bilan ____________________________

    def test_izoh_bilan():
        baholar = [45, 50, 55]
        natija = ortacha(baholar)
>       assert natija >= 60, f"guruh o'rtachasi past: {natija:.1f} ({baholar})"
E       AssertionError: guruh o'rtachasi past: 50.0 ([45, 50, 55])
E       assert 50.0 >= 60

test_izoh.py:7: AssertionError
======================= short test summary info ========================
FAILED test_izoh.py::test_izoh_bilan - AssertionError: guruh o'rtacha...
1 failed in 0.01s

Izoh avtomatik xabardan oldin chiqadi. Bu ayniqsa assert x > 0 kabi mazmunsiz solishtirishlarda qo'l keladi: assert 50.0 >= 60 qatori xatoning nima ekanini aytmaydi, izoh esa aytadi.

Qaysi qiymatga qaysi tekshiruv Butun son, satr, ro'yxat assert a == b To'g'ridan-to'g'ri tenglik pytest farqni o'zi topadi Lug'atda faqat farq qilgan kalitlar ko'rsatiladi Kasr son assert a == approx(b) Tenglik ishlamaydi: 0.1 + 0.2 != 0.3 rel= nisbiy, abs= mutlaq farqni belgilaydi Xato holati with raises(X, match=) Blok ichida faqat bitta chaqiruv bo'lsin match= doimiy ifoda - qavslarni himoyalang Uchchalasida ham maxsus metod emas, oddiy Python operatorlari ishlatiladi
Uchta holat - uchta yondashuv
Bitta testda nechta assert bo'lishi kerak

Qat'iy qoida yo'q, lekin foydali o'lchov bor: testning nomi bitta gap bo'lsa, shu gapni isbotlaydigan barcha assertlar joiz.

test_royxatlar ichidagi uchta assert - bitta fikrning uch tomoni, bu normal. Lekin bitta testda ham o'rtachani, ham xatoni, ham saqlashni tekshirsangiz - bu uchta alohida test bo'lishi kerak.

Sabab amaliy: birinchi assert yiqilsa, keyingilari umuman bajarilmaydi. Ya'ni siz bitta ishga tushirishda bitta xato ko'rasiz, qolganlari yashirin qoladi.

Amaliy topshiriq
  1. test_solishtirish.py dagi to'rtta testni o'zingizda ishga tushiring.
  2. Ro'yxat testidagi bitta elementni o'zgartiring va farq hisobotini o'qing.
  3. Lug'at testiga to'rtinchi kalit qo'shing va xato qanday ko'rinishini yozing.
  4. python3 -c "print(0.1 + 0.2)" ni ishga tushirib, chiqqan sonni to'liq yozib oling.
  5. Uni pytest.approx bilan tuzating.
  6. approx(100, rel=0.01) ni 101 uchun sinab ko'ring - o'tadimi?
  7. ortacha([]) uchun pytest.raises bilan test yozing.
  8. match= ga mos kelmaydigan matn bering va xatoni o'qing.
  9. pytest.raises blokiga ikkita chaqiruv yozing va nima uchun bu xavfli ekanini tushuntiring.
  10. O'z izohingiz bilan yiqiladigan bitta test yozing va xabarni o'qing.

Xulosa #

  • pytest da maxsus tasdiqlash metodlari yo'q - ==, in, >, not yetarli.
  • Xato xabari turga qarab o'zgaradi: ro'yxatda indeks, lug'atda farq qilgan kalit ko'rsatiladi.
  • Kasr sonlarni == bilan solishtirish ertami-kechmi yiqiladi - 0.1 + 0.2 != 0.3.
  • Sabab - ikkilik sanoqda kasr sonlar aniq ifodalanmasligi; bu Python ning emas, kompyuterning xususiyati.
  • Testlarda yechim pytest.approx, oddiy kodda - butun son yoki Decimal.
  • approx ro'yxat va lug'at bilan ham ishlaydi; rel= nisbiy, abs= mutlaq farqni belgilaydi.
  • Funksiyaning xato holatlari ham tekshirilishi kerak - bu ishning yarmi.
  • pytest.raises(ValueError, match="matn") bitta qatorda ham turni, ham xabarni tekshiradi.
  • with pytest.raises(...) bloki ichida faqat bitta chaqiruv bo'lsin - qolgan qatorlar bajarilmaydi.
  • match= doimiy ifoda sifatida o'qiladi; maxsus belgilar re.escape() bilan himoyalanadi.
  • Istisno tashlanmasa, pytest DID NOT RAISE deb xabar beradi.
  • assert shart, "izoh" - avtomatik xabarni to'ldiradigan o'z izohingiz.
  • Bitta testda bir nechta assert joiz, lekin ular bitta fikrni isbotlashi kerak.

Keyingi bo'limda katta savolga o'tamiz: umuman nimani testlash kerak va nimani kerak emas.

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.