Testlar: pytest bilan boshlash
Nega test yozish kerak
Eng keng tarqalgan javob — "xatolarni topish uchun". Bu to'g'ri, lekin asosiysi emas.
Asosiysi: testsiz kodni o'zgartirib bo'lmaydi. Ishlayotgan 500 qatorli faylga tegishdan qo'rqasiz, chunki nima buzilishini bilmaysiz. Testlar bu qo'rquvni yo'q qiladi — o'zgartirasiz, ishga tushirasiz, javobni ko'rasiz.
Hozirgacha kodni qo'lda tekshirdik: print qo'ydik, chiqishga qaradik. Muammo shundaki, bu
takrorlanmaydi. Ertaga yana qarash kerak, va o'tgan safar nima to'g'ri bo'lganini eslash kerak.
Dev bog'liqliklarni o'rnatamiz
2-bobda pyproject.toml ga dev guruhini yozgandik. Endi u kerak bo'ldi:
.venv/bin/python3 -m pip install --quiet -e ".[dev]" .venv/bin/python3 -m pytest --version
pytest 9.1.1
Birinchi test
pytest qoidalari sodda: fayl nomi test_*.py, funksiya nomi test_*, tekshiruv — oddiy assert.
shu faylni yaratingtests/test_tekshiruv.py
from hisobot.tekshiruv import bahoni_tekshir def test_oddiy_baho(): assert bahoni_tekshir("85") == 85 def test_bosh_joylar_bilan(): assert bahoni_tekshir(" 90 ") == 90
.venv/bin/python3 -m pytest tests/ -v
============================= test session starts ============================== platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0 -- /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/.venv/bin/python3 cachedir: .pytest_cache rootdir: /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish configfile: pyproject.toml plugins: cov-7.1.0 collecting ... collected 2 items tests/test_tekshiruv.py::test_oddiy_baho PASSED [ 50%] tests/test_tekshiruv.py::test_bosh_joylar_bilan PASSED [100%] ============================== 2 passed in 0.22s ===============================
Ikkala test ham o'tdi. int(" 90 ") bo'sh joylarni o'zi tashlab yuboradi — buni bilmagan bo'lsak,
test buni hujjatlashtirib qo'ydi.
Xato holatini sinash
Muhimi shundaki, kod to'g'ri ishlashi yetarli emas — u to'g'ri yiqilishi ham kerak.
shu faylni yaratingtests/test_tekshiruv.py
import pytest from hisobot.tekshiruv import bahoni_tekshir def test_oddiy_baho(): assert bahoni_tekshir("85") == 85 def test_bosh_joylar_bilan(): assert bahoni_tekshir(" 90 ") == 90 def test_son_emas(): with pytest.raises(ValueError, match="son emas"): bahoni_tekshir("abc") def test_bosh_qiymat(): with pytest.raises(ValueError): bahoni_tekshir("") def test_yoq_qiymat(): with pytest.raises(ValueError, match="ko'rsatilmagan"): bahoni_tekshir(None) def test_chegaradan_yuqori(): with pytest.raises(ValueError, match="oralig'ida"): bahoni_tekshir("150") def test_manfiy(): with pytest.raises(ValueError, match="oralig'ida"): bahoni_tekshir("-5")
.venv/bin/python3 -m pytest tests/ -v
============================= test session starts ============================== platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0 -- /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/.venv/bin/python3 cachedir: .pytest_cache rootdir: /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish configfile: pyproject.toml plugins: cov-7.1.0 collecting ... collected 7 items tests/test_tekshiruv.py::test_oddiy_baho PASSED [ 14%] tests/test_tekshiruv.py::test_bosh_joylar_bilan PASSED [ 28%] tests/test_tekshiruv.py::test_son_emas PASSED [ 42%] tests/test_tekshiruv.py::test_bosh_qiymat PASSED [ 57%] tests/test_tekshiruv.py::test_yoq_qiymat PASSED [ 71%] tests/test_tekshiruv.py::test_chegaradan_yuqori PASSED [ 85%] tests/test_tekshiruv.py::test_manfiy PASSED [100%] ============================== 7 passed in 0.08s ===============================
pytest.raises ikki narsani tekshiradi: xato turi va xabarining mazmuni (match — bu
regulyar ifoda). Ikkinchisi muhim, chunki ValueError har xil sababdan chiqishi mumkin, siz esa
aniq qaysinisini kutayotganingizni ayta olasiz.
Test yiqilganda nima ko'rasiz
pytest ning eng kuchli tomoni — xato xabari. Ataylab noto'g'ri test yozamiz:
shu faylni yaratingnamuna/test_yiqiladi.py
from hisobot.tekshiruv import bahoni_tekshir def test_ataylab_notogri(): assert bahoni_tekshir("85") == 58 def test_royxat_solishtirish(): kutilgan = ["Aziz", "Bobur", "Dilnoza"] olingan = ["Aziz", "Bobur", "Elyor"] assert olingan == kutilgan
.venv/bin/python3 -m pytest namuna/test_yiqiladi.py
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0
rootdir: /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish
configfile: pyproject.toml
plugins: cov-7.1.0
collected 2 items
namuna/test_yiqiladi.py FF [100%]
=================================== FAILURES ===================================
_____________________________ test_ataylab_notogri _____________________________
def test_ataylab_notogri():
> assert bahoni_tekshir("85") == 58
E AssertionError: assert 85 == 58
E + where 85 = bahoni_tekshir('85')
namuna/test_yiqiladi.py:5: AssertionError
___________________________ test_royxat_solishtirish ___________________________
def test_royxat_solishtirish():
kutilgan = ["Aziz", "Bobur", "Dilnoza"]
olingan = ["Aziz", "Bobur", "Elyor"]
> assert olingan == kutilgan
E AssertionError: assert ['Aziz', 'Bobur', 'Elyor'] == ['Aziz', 'Bobur', 'Dilnoza']
E
E At index 2 diff: 'Elyor' != 'Dilnoza'
E Use -v to get more diff
namuna/test_yiqiladi.py:11: AssertionError
=========================== short test summary info ============================
FAILED namuna/test_yiqiladi.py::test_ataylab_notogri - AssertionError: assert...
FAILED namuna/test_yiqiladi.py::test_royxat_solishtirish - AssertionError: as...
============================== 2 failed in 0.99s ===============================
Diqqat qiling: pytest qiymatlarni ko'rsatdi — 85 == 58 emasligini va ro'yxatlarning qaysi
indeksda farq qilishini (At index 2 diff). Bu oddiy assert bilan bo'lmaydi; pytest testni
qayta yozib, qiymatlarni chiqaradi.
Shuning uchun assert x == y yozish kifoya — assertEqual kabi maxsus funksiyalar kerak emas.
Bu faylni tests/ ichiga qo'ymadik, namuna/ ga qo'ydik — u ataylab yiqiladi va doimiy
to'plamga kirmasligi kerak. pyproject.toml dagi testpaths = ["tests"] uni o'zi ham yig'maydi.
ls tests/ namuna/
namuna/: __pycache__ test_yiqiladi.py tests/: __pycache__ test_tekshiruv.py
TDD: avval test, keyin kod
Endi yangi talab keldi: sana formati tekshirilsin (YYYY-MM-DD). TDD tartibi — avval test.
1-qadam: RED. Test yozamiz, u yiqilishi kerak:
shu faylni yaratingtests/test_sana.py
import pytest from hisobot.tekshiruv import sanani_tekshir def test_togri_sana(): assert sanani_tekshir("2026-09-01") == "2026-09-01" def test_notogri_format(): with pytest.raises(ValueError, match="format"): sanani_tekshir("01.09.2026") def test_mavjud_bolmagan_sana(): with pytest.raises(ValueError): sanani_tekshir("2026-02-30") def test_bosh(): with pytest.raises(ValueError): sanani_tekshir("")
.venv/bin/python3 -m pytest tests/test_sana.py
============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0
rootdir: /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish
configfile: pyproject.toml
plugins: cov-7.1.0
collected 0 items / 1 error
==================================== ERRORS ====================================
_____________________ ERROR collecting tests/test_sana.py ______________________
ImportError while importing test module '/mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/tests/test_sana.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.10/importlib/__init__.py:126: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_sana.py:3: in <module>
from hisobot.tekshiruv import sanani_tekshir
E ImportError: cannot import name 'sanani_tekshir' from 'hisobot.tekshiruv' (/mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/src/hisobot/tekshiruv.py)
=========================== short test summary info ============================
ERROR tests/test_sana.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 2.00s ===============================
Qaytish kodi 2 — ImportError, chunki sanani_tekshir hali yo'q. Bu kutilgan: RED.
2-qadam: GREEN. Eng oddiy ishlaydigan kodni yozamiz:
shu faylni yaratingsrc/hisobot/tekshiruv.py
"""Qoidalar: qator to'g'rimi yoki yo'q.""" from datetime import date ENG_KATTA_BAHO = 100 def bahoni_tekshir(xom: str | None) -> int: """Matnni bahoga aylantiradi. Xato bo'lsa sabab bilan ValueError.""" if xom is None: raise ValueError("baho ko'rsatilmagan") try: baho = int(xom) except (TypeError, ValueError): raise ValueError(f"baho son emas: {xom!r}") from None if not 0 <= baho <= ENG_KATTA_BAHO: raise ValueError(f"baho 0..{ENG_KATTA_BAHO} oralig'ida bo'lishi kerak, berilgan: {baho}") return baho def sanani_tekshir(xom: str | None) -> str: """YYYY-MM-DD formatini tekshiradi va o'sha matnni qaytaradi.""" if not xom: raise ValueError("sana ko'rsatilmagan") try: date.fromisoformat(xom.strip()) except ValueError: raise ValueError(f"sana formati noto'g'ri (YYYY-MM-DD kutilgan): {xom!r}") from None return xom.strip()
.venv/bin/python3 -m pytest tests/ -v
============================= test session starts ============================== platform linux -- Python 3.10.12, pytest-9.1.1, pluggy-1.6.0 -- /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish/.venv/bin/python3 cachedir: .pytest_cache rootdir: /mnt/c/Users/Admin/Desktop/Course Material/Python_Muhandisligi/ish configfile: pyproject.toml plugins: cov-7.1.0 collecting ... collected 11 items tests/test_sana.py::test_togri_sana PASSED [ 9%] tests/test_sana.py::test_notogri_format PASSED [ 18%] tests/test_sana.py::test_mavjud_bolmagan_sana PASSED [ 27%] tests/test_sana.py::test_bosh PASSED [ 36%] tests/test_tekshiruv.py::test_oddiy_baho PASSED [ 45%] tests/test_tekshiruv.py::test_bosh_joylar_bilan PASSED [ 54%] tests/test_tekshiruv.py::test_son_emas PASSED [ 63%] tests/test_tekshiruv.py::test_bosh_qiymat PASSED [ 72%] tests/test_tekshiruv.py::test_yoq_qiymat PASSED [ 81%] tests/test_tekshiruv.py::test_chegaradan_yuqori PASSED [ 90%] tests/test_tekshiruv.py::test_manfiy PASSED [100%] ============================== 11 passed in 0.10s ==============================
GREEN — hammasi o'tdi. date.fromisoformat formatni ham, sananing mavjudligini ham tekshiradi,
shuning uchun 2026-02-30 rad etildi.
Diqqat: biz o'zimiz sana tahlilchisi yozmadik. TDD ning foydasi shu — test "nima kerakligini" belgilaydi, "qanday qilish" esa erkin qoladi va odatda standart kutubxonada tayyor javob bor.
Testlar qayerda turadi
find . -name "test_*.py" -not -path "./.venv/*" | sort cat tests/__init__.py 2>/dev/null || echo "(tests/__init__.py yo'q — bu to'g'ri)"
./namuna/test_yiqiladi.py ./tests/test_sana.py ./tests/test_tekshiruv.py (tests/__init__.py yo'q — bu to'g'ri)
tests/ papkasida __init__.py bo'lmasligi kerak — aks holda u paketga aylanadi va pytest ning
import mexanizmi chalkashadi.
Testlar src/ ichida emas, chunki ular paketga kirmaydi: foydalanuvchi pip install hisobot
qilganda testlar unga kerak emas.
Test nomlari — hujjat
.venv/bin/python3 -m pytest tests/ --collect-only -q
tests/test_sana.py::test_togri_sana tests/test_sana.py::test_notogri_format tests/test_sana.py::test_mavjud_bolmagan_sana tests/test_sana.py::test_bosh tests/test_tekshiruv.py::test_oddiy_baho tests/test_tekshiruv.py::test_bosh_joylar_bilan tests/test_tekshiruv.py::test_son_emas tests/test_tekshiruv.py::test_bosh_qiymat tests/test_tekshiruv.py::test_yoq_qiymat tests/test_tekshiruv.py::test_chegaradan_yuqori tests/test_tekshiruv.py::test_manfiy 11 tests collected in 0.06s
Test nomlari ro'yxati — bu kodning xulq-atvori haqidagi hujjat. "Nima qiladi?" degan savolga shu ro'yxat javob beradi.
Shuning uchun test_1, test_2 deb nomlamang. Nom sharoit va kutilgan natijani aytsin:
test_chegaradan_yuqori, test_bosh_qiymat.
Foydali bayroqlar
.venv/bin/python3 -m pytest tests/ -q .venv/bin/python3 -m pytest tests/ -q -k "sana" .venv/bin/python3 -m pytest tests/ -q --tb=no
........... [100%] 11 passed in 0.17s .... [100%] 4 passed, 7 deselected in 0.07s ........... [100%] 11 passed in 0.12s
| Bayroq | Nima qiladi |
|---|---|
-v |
Har test nomini ko'rsatadi |
-q |
Qisqa chiqish |
-k "matn" |
Nomida shu matn bor testlarni ishga tushiradi |
-x |
Birinchi yiqilishda to'xtaydi |
--tb=short / --tb=no |
Xato izini qisqartiradi yoki yashiradi |
--lf |
Faqat o'tgan safar yiqilganlarni qayta ishga tushiradi |
--lf (last failed) ishlab chiqishda eng foydalisi: tuzatasiz, faqat yiqilganlarni qayta chopasiz.
pytest ni sozlash
Har safar tests/ yozmaslik uchun sozlamani pyproject.toml ga qo'shamiz:
shu faylni yaratingpyproject.toml
[project]
name = "hisobot"
version = "0.6.0"
description = "CSV ma'lumotini tekshirib hisobot chiqaradigan asbob"
requires-python = ">=3.10"
dependencies = []
[project.optional-dependencies]
dev = [
"pytest>=8",
"pytest-cov>=5",
"mypy>=1.8",
"ruff>=0.5",
]
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q --strict-markers"
.venv/bin/python3 -m pytest
........... [100%] 11 passed in 0.08s
Endi pytest yetarli — u tests/ ni o'zi topadi.
--strict-markers nima uchun: @pytest.mark.slow kabi belgini noto'g'ri yozsangiz (@pytest.mark.slwo),
pytest uni jim e'tiborsiz qoldiradi. Bu bayroq bilan u xato beradi.
:::tip Test ham kod Testlar ham o'qiladi va saqlanadi. Ularga ham toza nom, qisqa tuzilish va takrorlanmaslik kerak. Lekin bitta farq bor: testda aql-idrok (shart, sikl, hisob) qancha kam bo'lsa shuncha yaxshi. Testda mantiq bo'lsa, o'sha mantiqning o'zini kim tekshiradi? :::
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Testning maqsadi | Xato topish emas, o'zgartirishga imkon berish |
| Nomlash | test_*.py, test_* funksiya; nom sharoitni aytsin |
assert |
Oddiy assert yetarli — pytest qiymatlarni o'zi ko'rsatadi |
pytest.raises |
Xato turi va xabar mazmuni tekshiriladi |
| RED → GREEN | Avval yiqiladigan test, keyin uni o'tkazadigan eng oddiy kod |
tests/ |
src/ dan tashqarida; __init__.py qo'ymang |
--lf |
Faqat yiqilganlarni qayta ishga tushiradi |
addopts |
Sozlamani pyproject.toml ga yozing |
Talaba ishi
bahoni_tekshir("85.5")nima qiladi? Test yozing va natijaga qarab qaror qiling: bu xato bo'lishi kerakmi yoki 85 ga yaxlitlanishi kerakmi?sanani_tekshirga kelajakdagi sana berilsa nima bo'ladi? TDD bilan "kelajak sana rad etilsin" qoidasini qo'shing.test_bosh_qiymatdamatchyo'q. Unga mosmatchqo'shing va testni yiqitadigan qilibtekshiruv.pydagi xabarni o'zgartiring. Xato xabari qanday ko'rinadi?pytest -x --lfni sinab ko'ring: ataylab ikkita testni buzing, keyin bittasini tuzating. Qaysi testlar qayta ishga tushdi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.