Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Test topish (discovery)
- 2.2. assert diagnostikasi
- 2.3. pytest.raises — istisnolarni tekshirish
- 2.4. pytest.approx — float taqqoslash
- 2.5. Testlarni belgilash (mark)
- 2.6. Test sinflari
- 2.7. Ishga tushirish variantlari
- 2.8. Chiqish kodi
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Test topish va assert diagnostikasi
- Misol 2 — pytest.raises va pytest.approx
- Misol 3 — Belgilar, sinflar va tanlab ishga tushirish
- Misol 4 — Amaliy: tashkil qilingan test to'plami
- 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.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) -
assertdiagnostikasi —pytestning 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 |
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 test2.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:
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:
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 ham2.5. Testlarni belgilash (mark)
@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:
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
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 tracebackQoidalar
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'xtash4. Batafsil misollar
Misollarda
pytestni deterministik ishga tushiruvchipytest_ishgayordamchisi ishlatiladi.
Misol 1 — Test topish va assert diagnostikasi
"""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:
=== 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: 1Nima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — pytest.raises va pytest.approx
"""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:
=== 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: 0Nima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 3 — Belgilar, sinflar va tanlab ishga tushirish
"""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:
=== 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_nolNima 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.
"""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:
=== 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: 0Nima 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
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
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
with pytest.raises(ValueError, match="narx (so'm)"): # ❌ ( regex
with pytest.raises(ValueError, match=re.escape("narx (so'm)")): # ✅4. Float ni == bilan
assert hisobla() == 0.3 # ❌ float
assert hisobla() == pytest.approx(0.3) # ✅5. Test funksiyasidan return
def test_narx():
return yakuniy_narx(100, 50) == 50 # ❌ pytest ogohlantiradi
def test_narx():
assert yakuniy_narx(100, 50) == 50 # ✅6. Test sinfida __init__
class TestNarx:
def __init__(self): # ❌ pytest o'tkazib yuboradi
self.narx = 100
class TestNarx:
def test_x(self): # ✅ har testda tayyorla (yoki fixture)
narx = 1007. Yiqilgan testni skip bilan yashirish
@pytest.mark.skip("yiqilyapti") # ❌ muammoni yashiradi
@pytest.mark.xfail(reason="ma'lum xato #123") # ✅ ko'rinadi, kuzatiladi7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 9-qism (o'tilgan): istisnolar —
pytest.raisesbilan 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 —
pytestchiqish kodi bilan avtomatik tekshirish
8. Eng yaxshi amaliyotlar
Oddiy
assertishlating —pytestdiagnostikani o'zi beradi.Istisnolarni
pytest.raisesbilan,match=orqali xabarni ham tekshiring.Float ni
pytest.approxbilan taqqoslang.Bog'liq testlarni sinflar bilan guruhlang (ixtiyoriy).
raisesblokini faqat xato kutilgan qatorga toraytiring.Hali tuzatilmagan xatoni
xfailbilan belgilang,skipbilan yashirmang.-kbilan tanlab,-xbilan tez to'xtatib rivojlanish davomida vaqt tejang.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:
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
PASSEDFAILEDPASSEDFAILED—ZeroDivisionErrorko'tarildi,ValueErrorkutilgan ediPASSEDFAILED— float aniqligiSKIPPEDXFAIL— yiqildi (kutilganidek)XPASS— o'tdi (kutilmaganda)SKIPPEDPASSED— xabar mos keldiFAILED— xabarmanfiyga mos kelmadi
Vazifa 2: Xatolarni tuzating
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() == kutilganJavoblar
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() == kutilganVazifa 3: To'liq test to'plami
Kalkulyator sinfi (qosh, ayir, kopaytir, bol, xotira) uchun:
- Har amal uchun testlar (musbat, manfiy, nol, float)
bol(x, 0)—pytest.raises(ZeroDivisionError)- Float natijalar —
pytest.approx - Testlarni sinflar bilan guruhlang;
pytest -vda toza chiqsin
Vazifa 4: Istisno testlari
Fayl o'qish funksiyasi turli xatolar beradi (yo'q fayl, ruxsat yo'q, buzuq format):
- Har xato turi uchun
pytest.raisesbilan test match=bilan xato xabarini ham tekshiring (re.escapekerak bo'lsa)- Istisno obyektiga kirib qo'shimcha ma'lumotni (
.filename) tekshiring xfailbilan hali qo'llab-quvvatlanmagan formatni belgilang
Vazifa 5: Belgilar tizimi
- Sekin testlarni
@pytest.mark.slowbilan belgilang (maxsus belgi) pytest -m "not slow"bilan ularni o'tkazib yuboring (pyproject.tomlda belgini ro'yxatlang)- Platformaga bog'liq testlarni
skipifbilan - Ma'lum xatolarni
xfailbilan; qaysi biriXPASSbo'lganini toping (tuzatilgan)
Vazifa 6: pytest chiqishini tahlil
Skript yozing (Misol'lardagi pytest_ishga asosida):
pytest --tb=no -qchiqishidan o'tgan/yiqilgan/o'tkazilgan sonlarni ajrating- Yiqilgan testlar ro'yxatini va sabablarini chiqaring
- Chiqish kodini tekshiring (0/1/5)
- 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
- Vosita marosimni kamaytirsa — qabul qilinadi
- "Niyatni ifodala, mexanizmni yashir" — yaxshi API belgisi
- Til falsafasiga mos vosita ekotizimda g'olib bo'ladi
- Eski standart yo'qolmaydi, lekin yangi loyihalar yaxshiroqni tanlaydi
6. Xulosa
pytestkamroq marosim, ko'proq diagnostika bilan g'olib bo'ldiassertqayta yozish — texnik sir- Python "oddiylik" falsafasiga mos
- Yangi loyiha —
pytest; eskiunittestham ishlaydi
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda pytest ning asosiy imkoniyatlarini o'rgandik.
Eng muhim uch fikr:
Oddiy
assert—pytestning kuchi.pytesttestlarni avtomatik topadi (test_*.py,test_funksiyalar,Test*sinflar) va oddiyassertbilan ishlaydi —unittestningself.assertEqualmetodlar to'plami kerak emas. Yiqilgandapytest"assertion rewriting" tufayli aynan nima farq qilganini ko'rsatadi:assert [1, 2, 3] == [1, 2, 4].Istisnolar va float. Kod xato ko'tarishi kerak bo'lgan holatni
pytest.raises(ValueError, match="...")bilan tekshiring —matchregex,as infobilan istisno obyektiga kirasiz. Float ni hech qachon==bilan emas,pytest.approxbilan taqqoslang.Belgilar, guruhlash va ishga tushirish.
skip/skipif— testni o'tkazib yuborish,xfail— yiqilishi kutilgan (hali tuzatilmagan) xato uchun (skipbilan yashirmang). Bog'liq testlarTest*sinflarda guruhlanadi (__init__siz). Rivojlanish davomida-kbilan tanlab,-xbilan 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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!