Asosiy qismga o'tish

Testlar: pytest bilan boshlash

6-bob 17 kod bloki 1850 so'z

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:

matn2 qator
.venv/bin/python3 -m pip install --quiet -e ".[dev]"
.venv/bin/python3 -m pytest --version
Chiqish (qaytish kodi 0, 26414 ms)
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

python9 qator
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
matn1 qator
.venv/bin/python3 -m pytest tests/ -v
Chiqish (qaytish kodi 0, 2505 ms)
============================= 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

python36 qator
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")
matn1 qator
.venv/bin/python3 -m pytest tests/ -v
Chiqish (qaytish kodi 0, 1221 ms)
============================= 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

python11 qator
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
matn1 qator
.venv/bin/python3 -m pytest namuna/test_yiqiladi.py
Chiqish (qaytish kodi 1, 4446 ms)
============================= 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.

matn1 qator
ls tests/ namuna/
Chiqish (qaytish kodi 0, 5 ms)
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

python22 qator
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("")
matn1 qator
.venv/bin/python3 -m pytest tests/test_sana.py
Chiqish (qaytish kodi 2, 4556 ms)
============================= 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

python28 qator
"""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()
matn1 qator
.venv/bin/python3 -m pytest tests/ -v
Chiqish (qaytish kodi 0, 2201 ms)
============================= 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

matn2 qator
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)"
Chiqish (qaytish kodi 0, 518 ms)
./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

matn1 qator
.venv/bin/python3 -m pytest tests/ --collect-only -q
Chiqish (qaytish kodi 0, 2288 ms)
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

python3 qator
.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
Chiqish (qaytish kodi 0, 6371 ms)
...........                                                              [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

python22 qator
[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"
matn1 qator
.venv/bin/python3 -m pytest
Chiqish (qaytish kodi 0, 2206 ms)
...........                                                              [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

  1. bahoni_tekshir("85.5") nima qiladi? Test yozing va natijaga qarab qaror qiling: bu xato bo'lishi kerakmi yoki 85 ga yaxlitlanishi kerakmi?
  2. sanani_tekshir ga kelajakdagi sana berilsa nima bo'ladi? TDD bilan "kelajak sana rad etilsin" qoidasini qo'shing.
  3. test_bosh_qiymat da match yo'q. Unga mos match qo'shing va testni yiqitadigan qilib tekshiruv.py dagi xabarni o'zgartiring. Xato xabari qanday ko'rinadi?
  4. pytest -x --lf ni 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.

Telegramda yozish Narxlar