IlmHamroh
Python kursi/Testlash2/10-dars20 daqiqa
Mundarija (22)

17.2-dars: pytest asoslari

17-QISM — TESTLASH · 2-dars


1. Kirish va motivatsiya

O'tgan darsda birinchi testni yozdik va pytest bilan ishga tushirdik. Endi pytest ni chuqurroq o'rganamiz — u nima uchun Python dunyosining standart test vositasi bo'lganini ko'ramiz. pytest ning kuchi soddaligida: oddiy assert, avtomatik test topish va yiqilishda ajoyib diagnostika.

Real vaziyat. Bir jamoa unittest (standart kutubxona) dan foydalanardi: har test self.assertEqual(a, b), self.assertIn(x, y) kabi maxsus metodlar talab qilardi va sinf ichida yozilardi. Test yiqilganda esa xabar quruq bo'lardi: "AssertionError". Ular pytest ga o'tishdi — endi oddiy assert a == b yetarli, va yiqilganda pytest aynan nima farq qilganini ko'rsatadi: assert [1, 2, 3] == [1, 2, 4]. Bu diagnostika xatoni topish vaqtini keskin qisqartirdi.

Bu darsda pytest ning asosiy imkoniyatlarini o'rganamiz: test topish, assert diagnostikasi, istisnolarni tekshirish, float taqqoslash, testlarni belgilash va tanlab ishga tushirish.

Bu darsda:

  • Test topish qoidalari (test_*, *_test)
  • assert diagnostikasi — pytest ning kuchi
  • pytest.raises — istisnolarni tekshirish
  • pytest.approx — float taqqoslash
  • Belgilar: skip, skipif, xfail
  • Test sinflari (Test* guruhlash)
  • Ishga tushirish: -k, -x, -v, -q
  • Amaliy: tashkil qilingan test to'plami

2. Nazariya — chuqur tushuntirish

2.1. Test topish (discovery)

pytest testlarni avtomatik topadi, ro'yxatdan o'tkazish shart emas:

Qoida Misol
Fayl test_*.py yoki *_test.py
Funksiya test_ bilan boshlanadi
Sinf Test bilan boshlanadi (__init__ siz)
Sinf metodi test_ bilan boshlanadi
bash
pytest                      # joriy papka va ost-papkalardan barcha testni topadi
pytest tests/               # faqat tests/ papkasidan
pytest test_narx.py         # bitta fayl
pytest test_narx.py::test_chegirmasiz   # bitta test

2.2. assert diagnostikasi

pytest assert ni "qayta yozadi" (assertion rewriting) — yiqilganda aynan nima farq qilganini ko'rsatadi:

Test Yiqilish xabari
assert [1,2,3] == [1,2,4] assert [1, 2, 3] == [1, 2, 4] (qaysi element farqi)
assert {"a":1} == {"a":2} assert {'a': 1} == {'a': 2}
assert "salom" == "salomat" satrlar farqi
assert 5 in [1,2,3] assert 5 in [1, 2, 3]

unittest da esa self.assertEqual(a, b), self.assertIn(...) kabi o'nlab metod kerak; pytest da faqat assert. Bu — pytest ning eng katta afzalligi.

2.3. pytest.raises — istisnolarni tekshirish

Kod xato ko'tarishi kerak bo'lgan holatni tekshirish:

python
import pytest

def test_nolga_bolish():
    with pytest.raises(ZeroDivisionError):
        1 / 0

def test_xabar_bilan():
    with pytest.raises(ValueError, match="manfiy"):    # xabar regex bilan mos
        yosh_tekshir(-5)

def test_istisno_malumoti():
    with pytest.raises(ValueError) as info:
        yosh_tekshir(-5)
    assert "manfiy" in str(info.value)                 # istisno obyektiga kirish

match= — regex, oddiy satr emas: maxsus belgilar (., (, $) ekranlanishi kerak (re.escape).

2.4. pytest.approx — float taqqoslash

Float aniqligi (2.5-qism) tufayli 0.1 + 0.2 == 0.3 yolg'on. Test uchun approx:

python
def test_float():
    assert 0.1 + 0.2 == pytest.approx(0.3)
    assert 100.0 == pytest.approx(100.001, abs=0.01)   # mutlaq tolerans
    assert [0.1, 0.2] == pytest.approx([0.1, 0.2])     # ro'yxat ham

2.5. Testlarni belgilash (mark)

python
@pytest.mark.skip(reason="hali tayyor emas")
def test_yangi_xususiyat(): ...

@pytest.mark.skipif(sys.platform == "win32", reason="faqat Linux")
def test_linux_yol(): ...

@pytest.mark.xfail(reason="ma'lum xato, keyin tuzatiladi")
def test_malum_muammo(): ...
Belgi Ma'nosi Natijada
skip Testni o'tkazib yubor SKIPPED
skipif(shart) Shartli o'tkazish SKIPPED/ishlaydi
xfail Yiqilishi kutilgan XFAIL (yiqilsa), XPASS (o'tsa)

xfail — hali tuzatilmagan xato uchun test yozib qo'yish; tuzatilgach XPASS bo'ladi va belgini olib tashlaysiz.

2.6. Test sinflari

Bog'liq testlarni guruhlash uchun Test bilan boshlanadigan sinf:

python
class TestChegirma:
    def test_chegirmasiz(self):
        assert yakuniy_narx(100, 0) == 100
    def test_toliq(self):
        assert yakuniy_narx(100, 100) == 0

Sinfda __init__ bo'lmasligi kerak — pytest uni o'zi yaratadi. Guruhlash ixtiyoriy; oddiy funksiyalar ham yetarli.

2.7. Ishga tushirish variantlari

Bayroq Ma'nosi
-v Batafsil — har test nomi bilan
-q Qisqa — faqat nuqta va yakun
-k "ifoda" Nomi bo'yicha tanlab (-k "chegirma and not toliq")
-x Birinchi yiqilishda to'xtash
--maxfail=N N yiqilishdan keyin to'xtash
--tb=short/line/no Traceback batafsilligi
-s print chiqishini ko'rsatish
--co Faqat topilgan testlarni ro'yxatlash (ishga tushirmasdan)

2.8. Chiqish kodi

pytest chiqish kodi — CI/CD (28-qism) uchun muhim:

Kod Ma'nosi
0 Hamma test o'tdi
1 Yiqilish bor
2 Ishga tushirish to'xtatildi
5 Hech qanday test topilmadi

3. Tez ma'lumotnoma

python
import pytest

def test_teng():
    assert funksiya(x) == kutilgan

def test_istisno():
    with pytest.raises(ValueError, match="xabar"):
        funksiya(notogri)

def test_float():
    assert hisobla() == pytest.approx(0.3)

@pytest.mark.skipif(shart, reason="...")
@pytest.mark.xfail(reason="...")
def test_belgilangan(): ...

class TestGuruh:
    def test_a(self): ...
pytest -v            # batafsil
pytest -k "narx"     # tanlab
pytest -x            # birinchi yiqilishda to'xta
pytest --tb=short    # qisqa traceback

Qoidalar

test_*.py fayllar, test_ funksiyalar, Test* sinflar
oddiy assert — self.assertEqual emas
istisno: pytest.raises(..., match=regex)
float: pytest.approx
skip/skipif/xfail — belgilar
-k bilan tanlab, -x bilan tez to'xtash

4. Batafsil misollar

Misollarda pytest ni deterministik ishga tushiruvchi pytest_ishga yordamchisi ishlatiladi.

Misol 1 — Test topish va assert diagnostikasi

python
"""pytest test topish; assert diagnostikasi — ro'yxat, lug'at, satr farqi aniq ko'rinadi; unittest bilan solishtirish (oddiy assert yetarli)."""

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 = '''
def test_royxat_farqi():
    assert [1, 2, 3] == [1, 2, 4]

def test_lugat_farqi():
    assert {"nom": "Aziz", "yosh": 25} == {"nom": "Aziz", "yosh": 26}

def test_satr_farqi():
    assert "salom" == "salomat"

def test_royxatda_yoq():
    assert 5 in [1, 2, 3]

def test_ozgaruvchi():
    natija = 2 + 2
    assert natija == 5
'''


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

        print("=== 1. Topilgan testlar (--co) ===")
        kod, satrlar = pytest_ishga(r, "--co", "-q")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")

        print("\n=== 2. assert diagnostikasi (--tb=no, short summary) ===")
        kod, satrlar = pytest_ishga(r, "--tb=no", "-q")
        for satr in satrlar:
            if satr.startswith("FAILED"):
                print(f"  {satr}")
        print("  ⭐ pytest AYNAN nima farq qilganini ko'rsatadi — unittest'da self.assertEqual kerak edi")
        print(f"  chiqish kodi: {kod}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Topilgan testlar (--co) ===
  test_diag.py::test_royxat_farqi
  test_diag.py::test_lugat_farqi
  test_diag.py::test_satr_farqi
  test_diag.py::test_royxatda_yoq
  test_diag.py::test_ozgaruvchi

=== 2. assert diagnostikasi (--tb=no, short summary) ===
  FAILED test_diag.py::test_royxat_farqi - assert [1, 2, 3] == [1, 2, 4]
  FAILED test_diag.py::test_lugat_farqi - AssertionError: assert {'nom': 'Aziz'...
  FAILED test_diag.py::test_satr_farqi - AssertionError: assert 'salom' == 'sal...
  FAILED test_diag.py::test_royxatda_yoq - assert 5 in [1, 2, 3]
  FAILED test_diag.py::test_ozgaruvchi - assert 4 == 5
  ⭐ pytest AYNAN nima farq qilganini ko'rsatadi — unittest'da self.assertEqual kerak edi
  chiqish kodi: 1

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

Misol 2 — pytest.raises va pytest.approx

python
"""pytest.raises: oddiy, match regex, istisno obyektiga kirish; pytest.approx float taqqoslash; xato yo'llarini tekshirish."""

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_tekshir(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 > 150:
        raise ValueError("yosh juda katta")
    return yosh

def foizni_qolla(narx, foiz):
    return narx * foiz / 100
'''

TEST = '''
import pytest
from validator import yosh_tekshir, foizni_qolla

def test_togri_yosh():
    assert yosh_tekshir(25) == 25

def test_manfiy_yosh_xato():
    with pytest.raises(ValueError):
        yosh_tekshir(-5)

def test_xato_xabari_match():
    with pytest.raises(ValueError, match="manfiy"):
        yosh_tekshir(-5)

def test_notogri_tur():
    with pytest.raises(TypeError, match="butun son"):
        yosh_tekshir("yigirma")

def test_istisno_obyekti():
    with pytest.raises(ValueError) as info:
        yosh_tekshir(200)
    assert "juda katta" in str(info.value)

def test_float_approx():
    assert foizni_qolla(100, 33.33) == pytest.approx(33.33)

def test_float_ozgarmas():
    # 0.1 + 0.2 != 0.3 (float) — approx bilan to'g'ri
    assert foizni_qolla(30, 1) + foizni_qolla(60, 1) == pytest.approx(0.9)
'''


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

        print("=== 1. Barcha 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}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Barcha test (-v) ===
  test_validator.py::test_togri_yosh PASSED
  test_validator.py::test_manfiy_yosh_xato PASSED
  test_validator.py::test_xato_xabari_match PASSED
  test_validator.py::test_notogri_tur PASSED
  test_validator.py::test_istisno_obyekti PASSED
  test_validator.py::test_float_approx PASSED
  test_validator.py::test_float_ozgarmas PASSED
  --- 7 passed ---
  chiqish kodi: 0

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

Misol 3 — Belgilar, sinflar va tanlab ishga tushirish

python
"""skip, skipif, xfail belgilar (SKIPPED/XFAIL/XPASS); test sinflari bilan guruhlash; -k bilan nom bo'yicha tanlash; -x bilan birinchi yiqilishda to'xtash."""

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 sys
import pytest

def qoshish(a, b):
    return a + b

class TestQoshish:
    def test_musbat(self):
        assert qoshish(2, 3) == 5
    def test_manfiy(self):
        assert qoshish(-1, -1) == -2
    def test_nol(self):
        assert qoshish(0, 0) == 0

@pytest.mark.skip(reason="funksiya hali yozilmagan")
def test_kelajakdagi_xususiyat():
    assert kopaytir(2, 3) == 6

@pytest.mark.skipif(sys.version_info < (3, 20), reason="faqat Python 3.20+")
def test_yangi_versiya():
    assert True

@pytest.mark.xfail(reason="ma'lum xato, keyin tuzatiladi")
def test_malum_muammo():
    assert qoshish(1, 1) == 3
'''


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

        print("=== 1. Barcha test — belgilar bilan ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")

        print("\n=== 2. -k bilan tanlash ('musbat yoki nol') ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no", "-k", "musbat or nol")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")

        print("\n=== 3. Faqat sinf ichidagilar (--co) ===")
        kod, satrlar = pytest_ishga(r, "--co", "-q", "-k", "TestQoshish")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Barcha test — belgilar bilan ===
  test_belgi.py::TestQoshish::test_musbat PASSED
  test_belgi.py::TestQoshish::test_manfiy PASSED
  test_belgi.py::TestQoshish::test_nol PASSED
  test_belgi.py::test_kelajakdagi_xususiyat SKIPPED (funksiya hali yoz...)
  test_belgi.py::test_yangi_versiya SKIPPED (faqat Python 3.20+)
  test_belgi.py::test_malum_muammo XFAIL (ma'lum xato, keyin tuzatiladi)
  --- 3 passed, 2 skipped, 1 xfailed ---

=== 2. -k bilan tanlash ('musbat yoki nol') ===
  test_belgi.py::TestQoshish::test_musbat PASSED
  test_belgi.py::TestQoshish::test_nol PASSED

=== 3. Faqat sinf ichidagilar (--co) ===
  test_belgi.py::TestQoshish::test_musbat
  test_belgi.py::TestQoshish::test_manfiy
  test_belgi.py::TestQoshish::test_nol

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

Misol 4 — Amaliy: tashkil qilingan test to'plami

Savat (shopping cart) mantiqini test qilamiz: mahsulot qo'shish, o'chirish, jami hisoblash, chegirma qo'llash. Testlarni sinflar bilan mantiqiy guruhlaymiz, chegara va xato holatlarini qamrab olamiz, raises va approx ni qo'llaymiz — real modul uchun to'liq, tashkil qilingan test to'plami.

python
"""savat modulini test qilish: sinflar bilan guruhlangan testlar (qo'shish, jami, chegirma, xatolar); raises, approx; chegara holatlari."""

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

KOD = '''
class Savat:
    def __init__(self):
        self._mahsulotlar = {}

    def qosh(self, nom, narx, soni=1):
        if narx < 0:
            raise ValueError("narx manfiy bo'la olmaydi")
        if soni < 1:
            raise ValueError("soni kamida 1 bo'lishi kerak")
        self._mahsulotlar[nom] = (narx, self._mahsulotlar.get(nom, (0, 0))[1] + soni)

    def ochir(self, nom):
        if nom not in self._mahsulotlar:
            raise KeyError(f"savatda yo'q: {nom}")
        del self._mahsulotlar[nom]

    def jami(self):
        return sum(narx * soni for narx, soni in self._mahsulotlar.values())

    def chegirmali_jami(self, foiz):
        if not 0 <= foiz <= 100:
            raise ValueError("chegirma 0-100 orasida")
        return self.jami() * (1 - foiz / 100)

    def __len__(self):
        return len(self._mahsulotlar)
'''

TEST = '''
import pytest
from savat import Savat

class TestQoshish:
    def test_bitta_mahsulot(self):
        s = Savat()
        s.qosh("kitob", 45000)
        assert len(s) == 1

    def test_bir_xil_mahsulot_soni_qoshiladi(self):
        s = Savat()
        s.qosh("kitob", 45000, 2)
        s.qosh("kitob", 45000, 3)
        assert s.jami() == 45000 * 5

    def test_manfiy_narx_xato(self):
        s = Savat()
        with pytest.raises(ValueError, match="manfiy"):
            s.qosh("kitob", -100)

    def test_nol_soni_xato(self):
        s = Savat()
        with pytest.raises(ValueError, match="kamida 1"):
            s.qosh("kitob", 100, 0)

class TestJami:
    def test_bosh_savat_jami_nol(self):
        assert Savat().jami() == 0

    def test_koplab_mahsulot(self):
        s = Savat()
        s.qosh("kitob", 45000)
        s.qosh("ruchka", 5000, 3)
        assert s.jami() == 45000 + 15000

class TestChegirma:
    def test_chegirmasiz(self):
        s = Savat()
        s.qosh("kitob", 100000)
        assert s.chegirmali_jami(0) == 100000

    def test_yarim_chegirma(self):
        s = Savat()
        s.qosh("kitob", 100000)
        assert s.chegirmali_jami(50) == pytest.approx(50000)

    def test_notogri_chegirma(self):
        s = Savat()
        with pytest.raises(ValueError):
            s.chegirmali_jami(150)

class TestOchirish:
    def test_mahsulot_ochirish(self):
        s = Savat()
        s.qosh("kitob", 45000)
        s.ochir("kitob")
        assert len(s) == 0

    def test_yoq_mahsulot_ochirish(self):
        s = Savat()
        with pytest.raises(KeyError):
            s.ochir("mavjud-emas")
'''


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 / "savat.py").write_text(KOD, encoding="utf-8")
        (r / "test_savat.py").write_text(TEST, encoding="utf-8")

        print("=== 1. To'liq test to'plami (-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=== 2. Faqat chegirma testlari (-k) ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=no", "-k", "Chegirma")
        for satr in satrlar:
            if "::" in satr:
                print(f"  {satr}")
        print(f"\n  umumiy chiqish kodi: {kod}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. To'liq test to'plami (-v) ===
  test_savat.py::TestQoshish::test_bitta_mahsulot PASSED
  test_savat.py::TestQoshish::test_bir_xil_mahsulot_soni_qoshiladi PASSED
  test_savat.py::TestQoshish::test_manfiy_narx_xato PASSED
  test_savat.py::TestQoshish::test_nol_soni_xato PASSED
  test_savat.py::TestJami::test_bosh_savat_jami_nol PASSED
  test_savat.py::TestJami::test_koplab_mahsulot PASSED
  test_savat.py::TestChegirma::test_chegirmasiz PASSED
  test_savat.py::TestChegirma::test_yarim_chegirma PASSED
  test_savat.py::TestChegirma::test_notogri_chegirma PASSED
  test_savat.py::TestOchirish::test_mahsulot_ochirish PASSED
  test_savat.py::TestOchirish::test_yoq_mahsulot_ochirish PASSED
  --- 11 passed ---

=== 2. Faqat chegirma testlari (-k) ===
  test_savat.py::TestChegirma::test_chegirmasiz PASSED
  test_savat.py::TestChegirma::test_yarim_chegirma PASSED
  test_savat.py::TestChegirma::test_notogri_chegirma PASSED

  umumiy chiqish kodi: 0

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


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

Noto'g'ri fikr To'g'risi
"pytest uchun self.assertEqual kerak" Oddiy assert yetarli
"Istisnoni try/except bilan test qilaman" pytest.raises toza va aniq
"match= oddiy satr" Regex — maxsus belgilar ekranlanadi
"Float ni == bilan test qilsa bo'ladi" pytest.approx kerak
"xfail — test o'chirilgan" Test ishlaydi, yiqilishi kutiladi
"Test sinfida __init__ bo'ladi" Yo'q — pytest o'zi yaratadi
"Barcha testni har safar ishga tushirish kerak" -k bilan tanlab ishga tushiring
"assert x yetarli xabar beradi" assert x == y — farqni ko'rsatadi

6. Keng tarqalgan xatolar va yechimlari

1. Istisnoni try/except bilan test qilish

python
def test_xato():                                    # ❌ noaniq
    try:
        yosh_tekshir(-5)
        assert False
    except ValueError:
        pass
def test_xato():                                    # ✅
    with pytest.raises(ValueError):
        yosh_tekshir(-5)

2. raises bloki juda keng

python
with pytest.raises(ValueError):                     # ❌ qaysi qator xato?
    x = tayyorla()
    natija = ishlat(x)
with pytest.raises(ValueError):                     # ✅ faqat xato kutilgan qator
    ishlat(x)

3. match= da maxsus belgi

python
with pytest.raises(ValueError, match="narx (so'm)"):   # ❌ ( regex
with pytest.raises(ValueError, match=re.escape("narx (so'm)")):  # ✅

4. Float ni == bilan

python
assert hisobla() == 0.3                             # ❌ float
assert hisobla() == pytest.approx(0.3)              # ✅

5. Test funksiyasidan return

python
def test_narx():
    return yakuniy_narx(100, 50) == 50              # ❌ pytest ogohlantiradi
def test_narx():
    assert yakuniy_narx(100, 50) == 50              # ✅

6. Test sinfida __init__

python
class TestNarx:
    def __init__(self):                             # ❌ pytest o'tkazib yuboradi
        self.narx = 100
class TestNarx:
    def test_x(self):                               # ✅ har testda tayyorla (yoki fixture)
        narx = 100

7. Yiqilgan testni skip bilan yashirish

python
@pytest.mark.skip("yiqilyapti")                     # ❌ muammoni yashiradi
@pytest.mark.xfail(reason="ma'lum xato #123")       # ✅ ko'rinadi, kuzatiladi

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 9-qism (o'tilgan): istisnolar — pytest.raises bilan test qilinadi
  • 17.3-dars: fixture — takroriy tayyorlashni ajratish
  • 17.4-dars: parametrize — bir testni ko'p ma'lumot bilan
  • 17.7-dars: coverage — testlar kodning qancha qismini qamragan
  • 18.4-dars: ruff — test kodini ham tekshiradi
  • 28-qism: CI/CD — pytest chiqish kodi bilan avtomatik tekshirish

8. Eng yaxshi amaliyotlar

  1. Oddiy assert ishlating — pytest diagnostikani o'zi beradi.

  2. Istisnolarni pytest.raises bilan, match= orqali xabarni ham tekshiring.

  3. Float ni pytest.approx bilan taqqoslang.

  4. Bog'liq testlarni sinflar bilan guruhlang (ixtiyoriy).

  5. raises blokini faqat xato kutilgan qatorga toraytiring.

  6. Hali tuzatilmagan xatoni xfail bilan belgilang, skip bilan yashirmang.

  7. -k bilan tanlab, -x bilan tez to'xtatib rivojlanish davomida vaqt tejang.

  8. Test nomlari xulqni tavsiflasin: test_manfiy_narx_xato_beradi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

Har test uchun PASSED, FAILED, SKIPPED, XFAIL yoki XPASS ni ayting:

python
import pytest, sys
1.  def test_a(): assert 2 + 2 == 4
2.  def test_b(): assert [1, 2] == [1, 3]
3.  def test_c():
        with pytest.raises(ZeroDivisionError): 1 / 0
4.  def test_d():
        with pytest.raises(ValueError): 1 / 0
5.  def test_e(): assert 0.1 + 0.2 == pytest.approx(0.3)
6.  def test_f(): assert 0.1 + 0.2 == 0.3
7.  @pytest.mark.skip(reason="x")
    def test_g(): assert False
8.  @pytest.mark.xfail(reason="x")
    def test_h(): assert 1 == 2
9.  @pytest.mark.xfail(reason="x")
    def test_i(): assert 1 == 1
10. @pytest.mark.skipif(True, reason="x")
    def test_j(): assert True
11. def test_k():
        with pytest.raises(ValueError, match="manfiy"):
            raise ValueError("manfiy son")
12. def test_l():
        with pytest.raises(ValueError, match="manfiy"):
            raise ValueError("katta son")
Javoblar
  1. PASSED
  2. FAILED
  3. PASSED
  4. FAILED — ZeroDivisionError ko'tarildi, ValueError kutilgan edi
  5. PASSED
  6. FAILED — float aniqligi
  7. SKIPPED
  8. XFAIL — yiqildi (kutilganidek)
  9. XPASS — o'tdi (kutilmaganda)
  10. SKIPPED
  11. PASSED — xabar mos keldi
  12. FAILED — xabar manfiy ga mos kelmadi

Vazifa 2: Xatolarni tuzating

python
1.  def test_xato():
        try:
            bolish(1, 0)
        except ZeroDivisionError:
            assert True

2.  def test_float():
        assert hisobla_soliq(1000) == 0.12

3.  def test_xabar():
        with pytest.raises(ValueError, match="narx (so'm)da"):
            tekshir(-5)

4.  class TestNarx:
        def __init__(self):
            self.narx = 100
        def test_chegirma(self):
            assert chegirma(self.narx, 10) == 90

5.  @pytest.mark.skip("hozircha yiqilyapti")
    def test_muhim_xususiyat():
        assert yangi_funksiya() == kutilgan
Javoblar
python
1.  def test_xato():
        with pytest.raises(ZeroDivisionError):
            bolish(1, 0)

2.  def test_float():
        assert hisobla_soliq(1000) == pytest.approx(0.12)

3.  import re
    def test_xabar():
        with pytest.raises(ValueError, match=re.escape("narx (so'm)da")):
            tekshir(-5)

4.  class TestNarx:
        def test_chegirma(self):
            narx = 100                          # __init__ emas
            assert chegirma(narx, 10) == 90

5.  @pytest.mark.xfail(reason="ma'lum xato #123, tuzatilmoqda")
    def test_muhim_xususiyat():
        assert yangi_funksiya() == kutilgan

Vazifa 3: To'liq test to'plami

Kalkulyator sinfi (qosh, ayir, kopaytir, bol, xotira) uchun:

  1. Har amal uchun testlar (musbat, manfiy, nol, float)
  2. bol(x, 0) — pytest.raises(ZeroDivisionError)
  3. Float natijalar — pytest.approx
  4. Testlarni sinflar bilan guruhlang; pytest -v da toza chiqsin

Vazifa 4: Istisno testlari

Fayl o'qish funksiyasi turli xatolar beradi (yo'q fayl, ruxsat yo'q, buzuq format):

  1. Har xato turi uchun pytest.raises bilan test
  2. match= bilan xato xabarini ham tekshiring (re.escape kerak bo'lsa)
  3. Istisno obyektiga kirib qo'shimcha ma'lumotni (.filename) tekshiring
  4. xfail bilan hali qo'llab-quvvatlanmagan formatni belgilang

Vazifa 5: Belgilar tizimi

  1. Sekin testlarni @pytest.mark.slow bilan belgilang (maxsus belgi)
  2. pytest -m "not slow" bilan ularni o'tkazib yuboring (pyproject.toml da belgini ro'yxatlang)
  3. Platformaga bog'liq testlarni skipif bilan
  4. Ma'lum xatolarni xfail bilan; qaysi biri XPASS bo'lganini toping (tuzatilgan)

Vazifa 6: pytest chiqishini tahlil

Skript yozing (Misol'lardagi pytest_ishga asosida):

  1. pytest --tb=no -q chiqishidan o'tgan/yiqilgan/o'tkazilgan sonlarni ajrating
  2. Yiqilgan testlar ro'yxatini va sabablarini chiqaring
  3. Chiqish kodini tekshiring (0/1/5)
  4. CI uchun qisqa hisobot (JSON) yarating

Vazifa 7: O'ylash

pytest Python standart kutubxonasidagi unittest (JUnit uslubidagi) o'rniga de-fakto standart bo'ldi, garchi unittest hech qayerga ketmagan bo'lsa ham. Nima uchun "oddiy assert" yondashuvi self.assertEqual uslubidan g'olib chiqdi, va bu Python falsafasi ("oddiy murakkabdan yaxshi") bilan qanday bog'liq?

Javob

Qisqa javob: pytest g'olib bo'ldi, chunki u kamroq marosim (boilerplate) talab qiladi va ko'proq beradi. unittest Java'ning JUnit'idan ko'chirilgan — sinflar, self.assertXxx metodlari, setUp/tearDown. pytest esa Python'cha: oddiy funksiya, oddiy assert, va diagnostikani vosita o'zi qo'shadi. Bu Python'ning "oddiylik" falsafasiga to'g'ri keladi: kod niyatni ifodalasin, mexanizm ko'rinmasin.

1. Nega assert yondashuvi g'olib bo'ldi

unittest pytest
self.assertEqual(a, b) assert a == b
self.assertTrue(x) assert x
self.assertIn(x, y) assert x in y
self.assertRaises(E) with pytest.raises(E)
O'nlab metod eslab qolish Bitta assert
Sinf majburiy Funksiya yetarli

2. pytest ning texnik siri

pytest "assertion rewriting" qiladi: test faylini import qilishda assert bayonotlarini o'zgartirib, yiqilganda ikala tomonni ko'rsatadigan kod qo'shadi. Shuning uchun oddiy assert a == b ham boy diagnostika beradi — bu unittest ning assertEqual metodlar to'plamiga bo'lgan ehtiyojini yo'q qiladi.

3. Python falsafasi bilan bog'liqlik

Zen of Python pytest'da
"Simple is better than complex" assert — sinf va metodlar emas
"Readability counts" Test niyatni to'g'ridan-to'g'ri ifodalaydi
"There should be one obvious way" Bitta assert, o'nlab metod emas
"Flat is better than nested" Funksiya — sinf ichida emas

4. unittest nega hali yashaydi

Sabab Tafsilot
Standart kutubxona O'rnatish shart emas
Mavjud kod Katta loyihalarda ko'p unittest testi bor
pytest mos ishlaydi pytest unittest testlarini ham ishga tushiradi
Ba'zi jamoalar odati Tanish uslub

5. Muhandislik saboqlari

  1. Vosita marosimni kamaytirsa — qabul qilinadi
  2. "Niyatni ifodala, mexanizmni yashir" — yaxshi API belgisi
  3. Til falsafasiga mos vosita ekotizimda g'olib bo'ladi
  4. Eski standart yo'qolmaydi, lekin yangi loyihalar yaxshiroqni tanlaydi

6. Xulosa

  1. pytest kamroq marosim, ko'proq diagnostika bilan g'olib bo'ldi
  2. assert qayta yozish — texnik sir
  3. Python "oddiylik" falsafasiga mos
  4. Yangi loyiha — pytest; eski unittest ham ishlaydi

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda pytest ning asosiy imkoniyatlarini o'rgandik.

Eng muhim uch fikr:

  1. Oddiy assert — pytest ning kuchi. pytest testlarni avtomatik topadi (test_*.py, test_ funksiyalar, Test* sinflar) va oddiy assert bilan ishlaydi — unittest ning self.assertEqual metodlar to'plami kerak emas. Yiqilganda pytest "assertion rewriting" tufayli aynan nima farq qilganini ko'rsatadi: assert [1, 2, 3] == [1, 2, 4].

  2. Istisnolar va float. Kod xato ko'tarishi kerak bo'lgan holatni pytest.raises(ValueError, match="...") bilan tekshiring — match regex, as info bilan istisno obyektiga kirasiz. Float ni hech qachon == bilan emas, pytest.approx bilan taqqoslang.

  3. Belgilar, guruhlash va ishga tushirish. skip/skipif — testni o'tkazib yuborish, xfail — yiqilishi kutilgan (hali tuzatilmagan) xato uchun (skip bilan yashirmang). Bog'liq testlar Test* sinflarda guruhlanadi (__init__ siz). Rivojlanish davomida -k bilan tanlab, -x bilan birinchi yiqilishda to'xtatib vaqt tejaysiz; chiqish kodi (0/1/5) CI/CD uchun muhim.

Keyingi darsda fixture larni o'rganamiz: har testda takrorlanadigan tayyorlash ishini (baza ulanishi, sinov ma'lumoti, vaqtinchalik fayl) ajratib, testlarni toza va mustaqil qilish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.2-dars: pytest asoslari — IlmHamroh