Asosiy qismga o'tish

Fixture, parametrize va qamrov

7-bob 20 kod bloki 1958 so'z

Takrorlanishdan qutulish

6-bobdagi testlarga qarang: test_son_emas, test_bosh_qiymat, test_chegaradan_yuqori, test_manfiy — to'rttasi ham bir xil tuzilishda, faqat kirish va kutilgan xabar boshqa.

parametrize shuni bitta testga yig'adi:

shu faylni yaratingtests/test_tekshiruv.py

python39 qator
import pytest

from hisobot.tekshiruv import bahoni_tekshir, sanani_tekshir


@pytest.mark.parametrize("xom, kutilgan", [
    ("85", 85),
    (" 90 ", 90),
    ("0", 0),
    ("100", 100),
    ("007", 7),
])
def test_togri_baholar(xom, kutilgan):
    assert bahoni_tekshir(xom) == kutilgan


@pytest.mark.parametrize("xom, xabar", [
    ("abc", "son emas"),
    ("", "son emas"),
    ("8.5", "son emas"),
    (None, "ko'rsatilmagan"),
    ("150", "oralig'ida"),
    ("-5", "oralig'ida"),
    ("101", "oralig'ida"),
])
def test_notogri_baholar(xom, xabar):
    with pytest.raises(ValueError, match=xabar):
        bahoni_tekshir(xom)


@pytest.mark.parametrize("xom", ["2026-09-01", "2000-01-01", "2026-12-31"])
def test_togri_sanalar(xom):
    assert sanani_tekshir(xom) == xom


@pytest.mark.parametrize("xom", ["01.09.2026", "2026-13-01", "2026-02-30", "", "kecha", None])
def test_notogri_sanalar(xom):
    with pytest.raises(ValueError):
        sanani_tekshir(xom)
matn1 qator
.venv/bin/python3 -m pytest tests/test_tekshiruv.py -v
Chiqish (qaytish kodi 0, 2302 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 21 items

tests/test_tekshiruv.py .....................                            [100%]

============================== 21 passed in 0.29s ==============================

Endi 5 + 7 + 3 + 6 = 21 holat, lekin kod to'rtta funksiya. Har holat alohida test sifatida ishlaydi va nomida o'z qiymati ko'rinadi — bittasi yiqilsa, aynan qaysi kirish muammo qilgani darhol ma'lum.

Bu naqshning asosiy foydasi: yangi holat qo'shish bir qator yozish demak.

matn2 qator
rm -f tests/test_sana.py
.venv/bin/python3 -m pytest -q
Chiqish (qaytish kodi 0, 2510 ms)
.....................                                                    [100%]

Fixture: tayyorgarlikni ajratish

Testga ba'zan tayyor ma'lumot kerak: fayl, ulanish, obyekt. fixture — shuni beradigan funksiya.

shu faylni yaratingtests/conftest.py

python28 qator
"""Barcha testlar uchun umumiy fixture lar. pytest bu faylni o'zi topadi."""
import pytest

from hisobot.modellar import Talaba


@pytest.fixture
def talabalar():
    """Sinov uchun uchta talaba."""
    return [
        Talaba(ism="Aziz", guruh="A", baho=85, sana="2026-09-01"),
        Talaba(ism="Bobur", guruh="A", baho=95, sana="2026-09-01"),
        Talaba(ism="Dilnoza", guruh="B", baho=70, sana="2026-09-02"),
    ]


@pytest.fixture
def csv_fayl(tmp_path):
    """Vaqtinchalik CSV fayl yaratadi va yo'lini qaytaradi."""
    yol = tmp_path / "sinov.csv"
    yol.write_text(
        "ism,guruh,baho,sana\n"
        "Aziz,A,85,2026-09-01\n"
        "Bobur,A,95,2026-09-01\n"
        "Xato,B,abc,2026-09-02\n",
        encoding="utf-8",
    )
    return yol

shu faylni yaratingtests/test_hisob.py

python24 qator
from hisobot.hisob import daraja_taqsimoti, eng_yaxshi, guruh_ortachasi


def test_guruh_ortachasi(talabalar):
    assert guruh_ortachasi(talabalar) == {"A": 90.0, "B": 70.0}


def test_guruh_ortachasi_bosh():
    assert guruh_ortachasi([]) == {}


def test_daraja_taqsimoti(talabalar):
    # Aziz 85 -> yaxshi, Bobur 95 -> a'lo, Dilnoza 70 -> qoniqarli
    assert daraja_taqsimoti(talabalar) == {"yaxshi": 1, "a'lo": 1, "qoniqarli": 1}


def test_eng_yaxshi(talabalar):
    eng = eng_yaxshi(talabalar)
    assert eng is not None
    assert eng.ism == "Bobur"


def test_eng_yaxshi_bosh():
    assert eng_yaxshi([]) is None
matn1 qator
.venv/bin/python3 -m pytest tests/test_hisob.py -v
Chiqish (qaytish kodi 0, 2014 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 5 items

tests/test_hisob.py .....                                                [100%]

============================== 5 passed in 0.06s ===============================

talabalar fixture nomi test funksiyasiga argument sifatida yozildi va pytest uni o'zi uzatdi. conftest.py da turgani uchun uni import qilish shart emas.

tmp_path: vaqtinchalik fayllar

tmp_path — pytest ning tayyor fixture i. U har testga yangi bo'sh papka beradi va keyin o'zi tozalaydi.

shu faylni yaratingtests/test_oqish.py

python29 qator
import pytest

from hisobot.oqish import oqi, tozala


def test_oqi_qatorlar_soni(csv_fayl):
    qatorlar = oqi(csv_fayl)
    assert len(qatorlar) == 3
    assert qatorlar[0]["ism"] == "Aziz"


def test_tozala_xatoni_ajratadi(csv_fayl):
    n = tozala(oqi(csv_fayl))
    assert len(n.talabalar) == 2
    assert len(n.xatolar) == 1
    assert n.jami == 3


def test_xato_qator_raqami(csv_fayl):
    n = tozala(oqi(csv_fayl))
    qator, ism, sabab = n.xatolar[0]
    assert qator == 4                      # sarlavha 1-qator, ma'lumot 2 dan boshlanadi
    assert ism == "Xato"
    assert "son emas" in sabab


def test_fayl_yoq(tmp_path):
    with pytest.raises(FileNotFoundError):
        oqi(tmp_path / "yoq.csv")
matn1 qator
.venv/bin/python3 -m pytest tests/test_oqish.py -v
Chiqish (qaytish kodi 0, 2234 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 4 items

tests/test_oqish.py ....                                                 [100%]

============================== 4 passed in 0.09s ===============================

Har test o'z papkasini oladi, shuning uchun ular bir-biriga xalaqit bermaydi va istalgan tartibda ishlaydi.

Buni ko'rsatamiz — papka yo'llarini chiqaramiz:

shu faylni yaratingtests/test_izolyatsiya.py

python8 qator
def test_birinchi(tmp_path):
    (tmp_path / "a.txt").write_text("birinchi")
    print("1-test papkasi:", tmp_path.name, "| fayllar:", [p.name for p in tmp_path.iterdir()])


def test_ikkinchi(tmp_path):
    print("2-test papkasi:", tmp_path.name, "| fayllar:", [p.name for p in tmp_path.iterdir()])
    assert not list(tmp_path.iterdir())
matn2 qator
.venv/bin/python3 -m pytest tests/test_izolyatsiya.py -v -s
rm tests/test_izolyatsiya.py
Chiqish (qaytish kodi 0, 1508 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

tests/test_izolyatsiya.py 1-test papkasi: test_birinchi0 | fayllar: ['a.txt']
.2-test papkasi: test_ikkinchi0 | fayllar: []
.

============================== 2 passed in 0.06s ===============================

Ikkinchi test bo'sh papka oldi — birinchisi yozgan fayl unga ko'rinmadi. Bu test izolyatsiyasi va u majburiy shart: testlar bir-biriga bog'liq bo'lsa, ularning natijasiga ishonib bo'lmaydi.

Fixture qamrovi (scope)

Qimmat tayyorgarlikni har testda takrorlash shart emas:

shu faylni yaratingtests/test_scope.py

python28 qator
import pytest

CHAQIRUVLAR = {"har_test": 0, "modul": 0}


@pytest.fixture
def har_test():
    CHAQIRUVLAR["har_test"] += 1
    return CHAQIRUVLAR["har_test"]


@pytest.fixture(scope="module")
def modul_uchun():
    CHAQIRUVLAR["modul"] += 1
    return CHAQIRUVLAR["modul"]


def test_a(har_test, modul_uchun):
    pass


def test_b(har_test, modul_uchun):
    pass


def test_c(har_test, modul_uchun):
    print(f"\nhar test uchun chaqirildi: {CHAQIRUVLAR['har_test']} marta")
    print(f"modul uchun chaqirildi   : {CHAQIRUVLAR['modul']} marta")
matn2 qator
.venv/bin/python3 -m pytest tests/test_scope.py -s -q
rm tests/test_scope.py
Chiqish (qaytish kodi 0, 3042 ms)
..
har test uchun chaqirildi: 3 marta
modul uchun chaqirildi   : 1 marta
.

Uch testda har_test uch marta, modul_uchun bir marta chaqirildi.

scope Qachon qayta yaratiladi
function (odatiy) Har test uchun
class Har sinf uchun
module Har fayl uchun
session Butun ishga tushirish uchun bir marta

Ehtiyot bo'ling: keng qamrovli fixture o'zgartirilsa, testlar bir-biriga ta'sir qiladi. Uni faqat o'zgarmas narsalar uchun ishlating (ulanish, o'qish uchun ma'lumot).

Qamrov (coverage): kod qancha sinaldi

Qamrovni o'lchash uchun pytest-cov kerak. U 6-bobdagi dev guruhga keyinroq qo'shilgan, shuning uchun uni hozir o'rnatamiz:

python2 qator
.venv/bin/python3 -m pip install --quiet pytest-cov
.venv/bin/python3 -m pytest --cov=hisobot --cov-report=term-missing
Chiqish (qaytish kodi 0, 11960 ms)
..............................                                           [100%]
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.10.12-final-0 _______________

Name                       Stmts   Miss  Cover   Missing
--------------------------------------------------------
src/hisobot/__init__.py        6      0   100%
src/hisobot/__main__.py       18     18     0%   2-24
src/hisobot/hisob.py          12      0   100%
src/hisobot/modellar.py       33      1    97%   20
src/hisobot/oqish.py          15      0   100%
src/hisobot/tekshiruv.py      20      0   100%
--------------------------------------------------------
TOTAL                        104     19    82%
30 passed in 0.39s

Missing ustuni — hech qachon bajarilmagan qatorlar. Ular sinovsiz kod.

Qamrovni foizga aylantirib, chegara qo'yish mumkin:

shu faylni yaratingpyproject.toml

python33 qator
[project]
name = "hisobot"
version = "0.7.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"

[tool.coverage.run]
source = ["hisobot"]

[tool.coverage.report]
show_missing = true
exclude_lines = [
    "pragma: no cover",
    "if __name__ == .__main__.:",
    "raise NotImplementedError",
]
python1 qator
.venv/bin/python3 -m pytest --cov=hisobot --cov-report=term-missing --cov-fail-under=80
Chiqish (qaytish kodi 0, 3664 ms)
..............................                                           [100%]
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.10.12-final-0 _______________

Name                       Stmts   Miss  Cover   Missing
--------------------------------------------------------
src/hisobot/__init__.py        6      0   100%
src/hisobot/__main__.py       16     16     0%   2-20
src/hisobot/hisob.py          12      0   100%
src/hisobot/modellar.py       33      1    97%   20
src/hisobot/oqish.py          15      0   100%
src/hisobot/tekshiruv.py      20      0   100%
--------------------------------------------------------
TOTAL                        102     17    83%
Required test coverage of 80% reached. Total coverage: 83.33%
30 passed in 0.37s

--cov-fail-under=80 — qamrov 80% dan past bo'lsa pytest xato qaytaradi. CI da shu ishlatiladi.

Qamrov yolg'on gapirishi mumkin

100% qamrov "xato yo'q" degani emas. Buni ko'rsatamiz:

shu faylni yaratingyomon_test.py

python2 qator
def bol(a, b):
    return a / b

shu faylni yaratingnamuna/test_yomon.py

python5 qator
from yomon_test import bol


def test_bol():
    bol(10, 2)          # natija tekshirilmagan!
python1 qator
.venv/bin/python3 -m pytest namuna/test_yomon.py --cov=yomon_test --cov-report=term-missing -q
Chiqish (qaytish kodi 0, 3889 ms)
.                                                                        [100%]
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.10.12-final-0 _______________

Name            Stmts   Miss  Cover   Missing
---------------------------------------------
yomon_test.py       2      0   100%
---------------------------------------------
TOTAL               2      0   100%

Qamrov 100% — lekin test hech narsani tekshirmaydi. bol(10, 0) nima qilishi ham noma'lum.

matn1 qator
.venv/bin/python3 -m pytest -q
Chiqish (qaytish kodi 0, 2922 ms)
..............................                                           [100%]

Saboq: qamrov — qaysi kod sinalmaganini ko'rsatadigan asbob, sifat o'lchovi emas. 60% dan past bo'lsa xavotir oling; 100% ni maqsad qilmang.

Butun to'plam

python2 qator
.venv/bin/python3 -m pytest --cov=hisobot --cov-report=term-missing
python3 -m mypy
Chiqish (qaytish kodi 0, 5350 ms)
..............................                                           [100%]
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.10.12-final-0 _______________

Name                       Stmts   Miss  Cover   Missing
--------------------------------------------------------
src/hisobot/__init__.py        6      0   100%
src/hisobot/__main__.py       16     16     0%   2-20
src/hisobot/hisob.py          12      0   100%
src/hisobot/modellar.py       33      1    97%   20
src/hisobot/oqish.py          15      0   100%
src/hisobot/tekshiruv.py      20      0   100%
--------------------------------------------------------
TOTAL                        102     17    83%
30 passed in 1.12s
Success: no issues found in 6 source files

Testlar o'tadi, turlar toza. Loyiha shu ikki buyruq bilan tekshiriladi — 16-bobda ularni CI ga qo'yamiz.

Xulosa

Tushuncha Asosiy fikr
parametrize Bir test, ko'p holat; har biri alohida ishlaydi
fixture Tayyorgarlikni ajratadi; argument nomi bilan chaqiriladi
conftest.py Umumiy fixture lar; import qilish shart emas
tmp_path Har testga yangi bo'sh papka — izolyatsiya kafolati
scope Qimmat tayyorgarlikni takrorlamaslik; lekin o'zgaruvchan bo'lmasin
Qamrov Qaysi qator sinalmaganini ko'rsatadi
Qamrov cheklovi 100% qamrov ham bo'sh test bilan olinadi — u sifat o'lchovi emas

Talaba ishi

  1. --cov-report=term-missing chiqishidagi Missing qatorlariga qarang. Ularning biriga test yozing. Qamrov necha foizga oshdi?
  2. csv_fayl fixture ini parametrize bilan birlashtiring: uchta turli xil CSV (bo'sh, faqat sarlavha, faqat xato qatorlar). Har biri uchun tozala nima qaytaradi?
  3. conftest.py ga scope="session" fixture qo'shing va uni ikki testda o'zgartiring. Qanday muammo yuzaga keladi?
  4. pytest --durations=5 ni ishga tushiring. Eng sekin testlar qaysilari va nega?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar