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

17.7-dars: Coverage

17-QISM — TESTLASH · 7-dars


1. Kirish va motivatsiya

Testlar yozdik — lekin ular kodning qancha qismini tekshiradi? Ba'zi funksiyalar, shartlar, xato yo'llari umuman test qilinmagan bo'lishi mumkin. Coverage (qamrov) — testlar kodning qaysi qatorlarini ishga tushirganini o'lchaydigan vosita. U test qilinmagan qatorlarni ko'rsatadi — ya'ni "ko'r nuqtalar" ni.

Coverage foydali, lekin xavfli ham: yuqori foizni "kod xatosiz" deb tushunish mumkin. Aslida coverage faqat qator ishga tushirilganini aytadi, u to'g'ri ishlaganini emas. Bu farqni tushunish — coverage ni to'g'ri ishlatishning kaliti.

Real vaziyat. Bir jamoa "90% coverage" maqsadini qo'ydi va unga yetdi — lekin ishlab chiqarishda hali ham xatolar chiqardi. Tekshirishda ma'lum bo'ldiki, testlar kodni ishga tushirar, lekin natijani jiddiy tekshirmasdi: funksiya(x) chaqirilar, lekin assert yo'q edi. Coverage 90% ko'rsatardi, chunki qatorlar bajarilardi — lekin xatolarni ushlamas edi. Boshqa tomondan, ular coverage hisobotida eng muhim funksiyaning xato yo'li (istisno) umuman test qilinmaganini ko'rishdi — coverage aynan shu ko'r nuqtani topib berdi. Coverage vositasi emas, uni qanday talqin qilish muhim ekan.

Bu darsda coverage ni o'lchashni, ko'r nuqtalarni topishni va foizni to'g'ri talqin qilishni o'rganamiz.

Bu darsda:

  • Coverage nima va nima o'lchaydi
  • coverage run va coverage report
  • Test qilinmagan qatorlarni topish (missing)
  • Tarmoqlanish qamrovi (branch coverage)
  • pytest-cov va sozlash
  • Qatorlarni chiqarib tashlash (pragma: no cover)
  • 100% coverage ≠ xatosiz kod
  • Amaliy: ko'r nuqtalarni topib yopish

ℹ Bu darsda coverage — tashqi kutubxona (pip install coverage) ishlatiladi.


2. Nazariya — chuqur tushuntirish

2.1. Coverage nima o'lchaydi

Coverage — testlar bajarilganda kodning qaysi qatorlari ishga tushganini yozib boradi:

Metrika Ma'nosi
Statement coverage Necha qator (bayonot) bajarildi
Branch coverage Har shart (if) ikkala tomoni tekshirildimi
Miss Bajarilmagan qatorlar
Cover Foiz (bajarilgan / jami)

Coverage faqat qator bajarilganini o'lchaydi, to'g'ri ishlaganini emas — bu muhim farq.

2.2. coverage run va report

bash
coverage run -m pytest         # testlarni coverage kuzatuvida ishga tushiradi
coverage report -m             # hisobot + qaysi qatorlar qamralmagan (-m = missing)
coverage html                  # brauzerda ko'riladigan batafsil hisobot

Namunaviy hisobot:

Name          Stmts   Miss  Cover   Missing
-------------------------------------------
kod.py           10      3    70%   5, 7, 11

Missing ustuni — aynan qaysi qatorlar test qilinmaganini ko'rsatadi (bu yerda 5, 7, 11-qatorlar).

2.3. Ko'r nuqtalarni topish

Coverage ning eng qimmatli imkoniyati — test qilinmagan qatorlarni ko'rsatish. Ko'pincha bular:

Ko'r nuqta Sabab
Xato yo'llari (except, raise) "Baxtli yo'l" test qilinadi, xato yo'li unutiladi
Chegara shartlari Kam uchraydigan if shohobchalari
Kam ishlatiladigan funksiyalar Yozilgan, lekin test qilinmagan
Himoya shartlari "Bu hech qachon bo'lmaydi" degan kod

Coverage bu nuqtalarni topib, qaysi testlar yetishmayotganini aniq ko'rsatadi.

2.4. Tarmoqlanish qamrovi (branch coverage)

Oddiy statement coverage if x: qatorini bajarilgan deb belgilaydi — lekin x False bo'lgan holat test qilinmagan bo'lishi mumkin. Branch coverage ikkala tomonni ham talab qiladi:

bash
coverage run --branch -m pytest
Name       Stmts   Miss Branch BrPart  Cover
kod.py        10      3      6      2    69%

Branch — jami tarmoqlanishlar, BrPart — qisman qamralgan (faqat bir tomoni). Branch coverage statement coverage dan qattiqroq va ishonchliroq.

2.5. pytest-cov va sozlash

pytest-cov plagini coverage ni to'g'ridan-to'g'ri pytest ga qo'shadi:

bash
pytest --cov=mening_paket --cov-report=term-missing

Sozlash pyproject.toml da:

toml
[tool.coverage.run]
branch = true
source = ["mening_paket"]

[tool.coverage.report]
show_missing = true
fail_under = 80              # 80% dan past bo'lsa xato (CI uchun)

fail_under — CI/CD (28-qism) da minimal qamrovni majburiy qiladi.

2.6. Qatorlarni chiqarib tashlash

Ba'zi qatorlarni (masalan if __name__ == "__main__", mudofaa kodi) qamrovdan chiqarish:

python
def himoya():
    if holat_bolmaydi:       # pragma: no cover
        raise RuntimeError("bu hech qachon bo'lmasligi kerak")

# pragma: no cover ni ehtiyotkorlik bilan — u qamrovni "yasama" oshirishi mumkin. Faqat haqiqatan test qilib bo'lmaydigan kod uchun.

2.7. Coverage ni to'g'ri talqin qilish

Noto'g'ri fikr To'g'ri talqin
"100% coverage = xatosiz" Faqat qator bajarilgan, natija tekshirilmagan bo'lishi mumkin
"Yuqori foiz — asosiy maqsad" Ma'noli testlar muhimroq
"Coverage past — yomon kod" Muhim qismlar qamralgani muhimroq
"Foizni oshirish uchun test yozish" Ko'r nuqtalarni ma'noli yopish

Coverage — ko'r nuqtalarni topish vositasi, sifat metrikasi emas. assert siz test coverage ni oshiradi, lekin xato ushlamaydi.

2.8. Qancha coverage kerak

Kod turi Maqsad
Biznes mantiq (kritik) Yuqori (90%+), ma'noli
Yordamchi kod O'rtacha
Prototip/skript Kam yoki umuman yo'q

100% ni maqsad qilish ko'pincha samarasiz: oxirgi 5-10% ba'zan test qilib bo'lmaydigan yoki arzimas kod. Muhim yo'llarni to'liq, ma'noli qamrash — foizni quvishdan yaxshiroq.


3. Tez ma'lumotnoma

bash
coverage run -m pytest              # coverage bilan test
coverage report -m                  # hisobot + missing qatorlar
coverage run --branch -m pytest     # tarmoqlanish qamrovi
coverage html                       # batafsil HTML hisobot

pytest --cov=paket --cov-report=term-missing   # pytest-cov bilan
toml
# pyproject.toml
[tool.coverage.run]
branch = true
source = ["paket"]
[tool.coverage.report]
show_missing = true
fail_under = 80

Qoidalar

coverage — ko'r nuqtalarni topish vositasi
missing ustuni — test qilinmagan qatorlar
branch coverage — statement dan ishonchliroq
100% coverage ≠ xatosiz (assert siz oshadi)
xato yo'llarini (except) qamrang — ko'p unutiladi
foizni quvma — muhim yo'llarni ma'noli qamra
pragma: no cover — ehtiyotkorlik bilan

4. Batafsil misollar

Misollarda coverage ni deterministik ishga tushiruvchi coverage_ol yordamchisi ishlatiladi.

Misol 1 — Coverage o'lchash va ko'r nuqtalar

python
"""coverage run + report; missing qatorlar test qilinmagan kodni ko'rsatadi; xato yo'li (except) qamralmagani aniqlanadi; test qo'shib yopish."""

import json
import subprocess
import sys
import tempfile
from pathlib import Path


def coverage_ol(katalog: Path, *args: str) -> dict:
    """coverage run + json; deterministik ma'lumot qaytaradi."""
    subprocess.run([sys.executable, "-m", "coverage", "run", *args, "-m", "pytest",
                    "-p", "no:cacheprovider", "-o", "addopts=", "-q"],
                   cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    r = subprocess.run([sys.executable, "-m", "coverage", "json", "-o", "-"],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    return json.loads(r.stdout)


KOD = '''
def toifala(son):
    if son < 0:
        return "manfiy"
    if son == 0:
        return "nol"
    if son > 100:
        return "katta"
    return "oddiy"

def xavfsiz_bol(a, b):
    if b == 0:
        raise ValueError("nolga bo'lib bo'lmaydi")
    return a / b
'''


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

        print("=== 1. Kam test — ko'r nuqtalar ko'p ===")
        (r / "test_kod.py").write_text(
            "from kod import toifala, xavfsiz_bol\n"
            "def test_manfiy(): assert toifala(-1) == 'manfiy'\n"
            "def test_oddiy(): assert toifala(50) == 'oddiy'\n"
            "def test_bol(): assert xavfsiz_bol(6, 2) == 3\n", encoding="utf-8")
        d = coverage_ol(r)
        fayl = d["files"]["kod.py"]
        print(f"  qamrov: {fayl['summary']['percent_covered_display']}%")
        print(f"  test qilinmagan qatorlar: {fayl['missing_lines']}")
        print("  ⚠️ 'nol', 'katta' shohobchalari va nolga bo'lish xatosi qamralmagan")

        print("\n=== 2. Ko'r nuqtalarni yopish ===")
        (r / "test_kod.py").write_text(
            "import pytest\n"
            "from kod import toifala, xavfsiz_bol\n"
            "def test_manfiy(): assert toifala(-1) == 'manfiy'\n"
            "def test_nol(): assert toifala(0) == 'nol'\n"
            "def test_katta(): assert toifala(200) == 'katta'\n"
            "def test_oddiy(): assert toifala(50) == 'oddiy'\n"
            "def test_bol(): assert xavfsiz_bol(6, 2) == 3\n"
            "def test_bol_nol():\n"
            "    with pytest.raises(ValueError):\n"
            "        xavfsiz_bol(1, 0)\n", encoding="utf-8")
        d = coverage_ol(r)
        fayl = d["files"]["kod.py"]
        print(f"  qamrov: {fayl['summary']['percent_covered_display']}%")
        print(f"  test qilinmagan qatorlar: {fayl['missing_lines'] or 'yo`q — hammasi qamraldi'}")
        print("  ⭐ coverage aynan qaysi test yetishmasligini ko'rsatdi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Kam test — ko'r nuqtalar ko'p ===
  qamrov: 75%
  test qilinmagan qatorlar: [6, 8, 13]
  ⚠️ 'nol', 'katta' shohobchalari va nolga bo'lish xatosi qamralmagan

=== 2. Ko'r nuqtalarni yopish ===
  qamrov: 100%
  test qilinmagan qatorlar: yo`q — hammasi qamraldi
  ⭐ coverage aynan qaysi test yetishmasligini ko'rsatdi

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

Misol 2 — Statement vs branch coverage

python
"""statement coverage 100% bo'lsa ham branch qamralmagan bo'lishi mumkin; branch coverage ikkala tomonni talab qiladi; qisman tarmoqlanish (BrPart)."""

import json
import subprocess
import sys
import tempfile
from pathlib import Path


def coverage_ol(katalog: Path, branch: bool) -> dict:
    args = ["--branch"] if branch else []
    subprocess.run([sys.executable, "-m", "coverage", "run", *args, "-m", "pytest",
                    "-p", "no:cacheprovider", "-o", "addopts=", "-q"],
                   cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    r = subprocess.run([sys.executable, "-m", "coverage", "json", "-o", "-"],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    return json.loads(r.stdout)


KOD = '''
def chegirma(narx, vip):
    natija = narx
    if vip:
        natija = narx * 0.9
    return natija
'''

# Test faqat vip=True ni sinaydi — 'if' ning False tomoni test qilinmaydi
TEST = '''
from kod import chegirma
def test_vip():
    assert chegirma(100, True) == 90
'''


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

        print("=== 1. Statement coverage ===")
        d = coverage_ol(r, branch=False)
        s = d["files"]["kod.py"]["summary"]
        print(f"  qamrov: {s['percent_covered_display']}% — barcha qator bajarildi")
        print("  ⚠️ lekin vip=False holati test qilinmagan!")

        print("\n=== 2. Branch coverage ===")
        d = coverage_ol(r, branch=True)
        s = d["files"]["kod.py"]["summary"]
        print(f"  qamrov: {s['percent_covered_display']}%")
        print(f"  jami tarmoqlanish: {s['num_branches']}, qisman qamralgan: {s['num_partial_branches']}")
        print("  ⭐ branch coverage vip=False qamralmaganini ochib berdi")

        print("\n=== 3. Ikkala tomonni test qilib ===")
        (r / "test_kod.py").write_text(
            "from kod import chegirma\n"
            "def test_vip(): assert chegirma(100, True) == 90\n"
            "def test_oddiy(): assert chegirma(100, False) == 100\n", encoding="utf-8")
        d = coverage_ol(r, branch=True)
        s = d["files"]["kod.py"]["summary"]
        print(f"  qamrov: {s['percent_covered_display']}%, qisman qamralgan: {s['num_partial_branches']}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Statement coverage ===
  qamrov: 100% — barcha qator bajarildi
  ⚠️ lekin vip=False holati test qilinmagan!

=== 2. Branch coverage ===
  qamrov: 86%
  jami tarmoqlanish: 2, qisman qamralgan: 1
  ⭐ branch coverage vip=False qamralmaganini ochib berdi

=== 3. Ikkala tomonni test qilib ===
  qamrov: 100%, qisman qamralgan: 0

Nima ko'rsatdi: 2.4-bo'lim.

Misol 3 — 100% coverage ≠ xatosiz kod

python
"""assert siz test coverage ni oshiradi, lekin xato ushlamaydi; 100% qamrov bilan ham buzuq kod o'tadi; ma'noli test farqi."""

import json
import subprocess
import sys
import tempfile
from pathlib import Path


def coverage_va_test(katalog: Path) -> tuple[dict, int]:
    natija = subprocess.run([sys.executable, "-m", "coverage", "run", "-m", "pytest",
                             "-p", "no:cacheprovider", "-o", "addopts=", "-q"],
                            cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    r = subprocess.run([sys.executable, "-m", "coverage", "json", "-o", "-"],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    return json.loads(r.stdout), natija.returncode


# Kodda XATO bor: qo'shish o'rniga ayirish
KOD = '''
def yakuniy_narx(narx, chegirma):
    return narx - chegirma / 100        # XATO: bu narxni deyarli o'zgartirmaydi
'''

# "Yomon" test: chaqiradi, lekin natijani JIDDIY tekshirmaydi
YOMON_TEST = '''
from kod import yakuniy_narx
def test_ishlaydi():
    natija = yakuniy_narx(100, 20)
    assert natija is not None            # ⚠️ faqat "None emas" — xatoni ushlamaydi
'''

# "Yaxshi" test: aniq natijani tekshiradi
YAXSHI_TEST = '''
from kod import yakuniy_narx
def test_chegirma():
    assert yakuniy_narx(100, 20) == 80   # aniq qiymat — xatoni ushlaydi
'''


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

        print("=== 1. 'Yomon' test — 100% coverage, lekin xato o'tadi ===")
        (r / "test_kod.py").write_text(YOMON_TEST, encoding="utf-8")
        d, kod = coverage_va_test(r)
        print(f"  qamrov: {d['files']['kod.py']['summary']['percent_covered_display']}%")
        print(f"  test natijasi: {'o`tdi' if kod == 0 else 'yiqildi'}")
        print("  ⚠️ 100% qamrov, lekin buzuq kod o'tib ketdi (assert is not None yetarli emas)")

        print("\n=== 2. 'Yaxshi' test — xatoni ushlaydi ===")
        (r / "test_kod.py").write_text(YAXSHI_TEST, encoding="utf-8")
        d, kod = coverage_va_test(r)
        print(f"  qamrov: {d['files']['kod.py']['summary']['percent_covered_display']}%")
        print(f"  test natijasi: {'o`tdi' if kod == 0 else 'yiqildi (xato ushlandi!)'}")
        print("  ⭐ bir xil coverage, lekin ma'noli assert xatoni topdi")

        print("\n=== 3. Xulosa ===")
        print("  coverage — qator bajarilganini o'lchaydi, to'g'riligini EMAS")
        print("  yuqori coverage + ma'nosiz assert = yolg'on xotirjamlik")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. 'Yomon' test — 100% coverage, lekin xato o'tadi ===
  qamrov: 100%
  test natijasi: o`tdi
  ⚠️ 100% qamrov, lekin buzuq kod o'tib ketdi (assert is not None yetarli emas)

=== 2. 'Yaxshi' test — xatoni ushlaydi ===
  qamrov: 100%
  test natijasi: yiqildi (xato ushlandi!)
  ⭐ bir xil coverage, lekin ma'noli assert xatoni topdi

=== 3. Xulosa ===
  coverage — qator bajarilganini o'lchaydi, to'g'riligini EMAS
  yuqori coverage + ma'nosiz assert = yolg'on xotirjamlik

Nima ko'rsatdi: 2.7-bo'lim.

Misol 4 — Amaliy: ko'r nuqtalarni topib yopish

Buyurtma holatini boshqaruvchi modulni test qilamiz. Avval mavjud (to'liq bo'lmagan) testlar bilan coverage o'lchaymiz, coverage ko'rsatgan ko'r nuqtalarni (xato yo'llari, chegara holatlari) aniqlaymiz va ularni ma'noli testlar bilan yopamiz. Bu — coverage ni real ish jarayonida ishlatishning namunasi: foizni quvish emas, yetishmayotgan muhim testlarni topish.

python
"""coverage bilan ko'r nuqtalarni topib, ma'noli test qo'shib yopish: xato yo'llari, chegara holatlari; branch coverage; foizni emas, muhim yo'llarni qamrash."""

import json
import subprocess
import sys
import tempfile
from pathlib import Path

KOD = '''
class BuyurtmaXatosi(Exception):
    pass

VALID_HOLATLAR = {"yangi", "tolangan", "yuborildi", "yetkazildi", "bekor"}
OTISHLAR = {
    "yangi": {"tolangan", "bekor"},
    "tolangan": {"yuborildi", "bekor"},
    "yuborildi": {"yetkazildi"},
    "yetkazildi": set(),
    "bekor": set(),
}

def holatni_ozgartir(joriy, yangi):
    if joriy not in VALID_HOLATLAR:
        raise BuyurtmaXatosi(f"noma'lum joriy holat: {joriy}")
    if yangi not in VALID_HOLATLAR:
        raise BuyurtmaXatosi(f"noma'lum yangi holat: {yangi}")
    if yangi not in OTISHLAR[joriy]:
        raise BuyurtmaXatosi(f"{joriy} dan {yangi} ga o'tib bo'lmaydi")
    return yangi

def bekor_qilsa_boladimi(holat):
    return holat in {"yangi", "tolangan"}
'''


def coverage_ol(katalog: Path) -> dict:
    subprocess.run([sys.executable, "-m", "coverage", "run", "--branch", "-m", "pytest",
                    "-p", "no:cacheprovider", "-o", "addopts=", "-q"],
                   cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    r = subprocess.run([sys.executable, "-m", "coverage", "json", "-o", "-"],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    return json.loads(r.stdout)


DASTLABKI_TEST = '''
from buyurtma import holatni_ozgartir
def test_tolash():
    assert holatni_ozgartir("yangi", "tolangan") == "tolangan"
def test_yuborish():
    assert holatni_ozgartir("tolangan", "yuborildi") == "yuborildi"
'''

TOLIQ_TEST = '''
import pytest
from buyurtma import holatni_ozgartir, bekor_qilsa_boladimi, BuyurtmaXatosi

def test_tolash():
    assert holatni_ozgartir("yangi", "tolangan") == "tolangan"
def test_yuborish():
    assert holatni_ozgartir("tolangan", "yuborildi") == "yuborildi"
def test_yetkazish():
    assert holatni_ozgartir("yuborildi", "yetkazildi") == "yetkazildi"
def test_bekor():
    assert holatni_ozgartir("yangi", "bekor") == "bekor"

def test_notogri_joriy_holat():
    with pytest.raises(BuyurtmaXatosi, match="joriy holat"):
        holatni_ozgartir("nomalum", "tolangan")
def test_notogri_yangi_holat():
    with pytest.raises(BuyurtmaXatosi, match="yangi holat"):
        holatni_ozgartir("yangi", "nomalum")
def test_ruxsatsiz_otish():
    with pytest.raises(BuyurtmaXatosi, match="o'tib bo'lmaydi"):
        holatni_ozgartir("yetkazildi", "yangi")

def test_bekor_qilsa_boladi():
    assert bekor_qilsa_boladimi("yangi") is True
def test_bekor_qilib_bolmaydi():
    assert bekor_qilsa_boladimi("yetkazildi") is False
'''


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

        print("=== 1. Dastlabki testlar bilan coverage ===")
        (r / "test_buyurtma.py").write_text(DASTLABKI_TEST, encoding="utf-8")
        d = coverage_ol(r)
        s = d["files"]["buyurtma.py"]["summary"]
        print(f"  qamrov: {s['percent_covered_display']}%")
        print(f"  test qilinmagan qatorlar: {d['files']['buyurtma.py']['missing_lines']}")
        print(f"  qisman tarmoqlanish: {s['num_partial_branches']}")
        print("  ⚠️ xato yo'llari va bekor_qilsa_boladimi umuman test qilinmagan")

        print("\n=== 2. Ko'r nuqtalarni ma'noli test bilan yopgach ===")
        (r / "test_buyurtma.py").write_text(TOLIQ_TEST, encoding="utf-8")
        d = coverage_ol(r)
        s = d["files"]["buyurtma.py"]["summary"]
        print(f"  qamrov: {s['percent_covered_display']}%")
        print(f"  test qilinmagan qatorlar: {d['files']['buyurtma.py']['missing_lines'] or 'yo`q'}")
        print(f"  qisman tarmoqlanish: {s['num_partial_branches']}")
        print("  ⭐ har xato yo'li va shohobcha ma'noli assert bilan qamraldi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Dastlabki testlar bilan coverage ===
  qamrov: 65%
  test qilinmagan qatorlar: [16, 18, 20, 24]
  qisman tarmoqlanish: 3
  ⚠️ xato yo'llari va bekor_qilsa_boladimi umuman test qilinmagan

=== 2. Ko'r nuqtalarni ma'noli test bilan yopgach ===
  qamrov: 100%
  test qilinmagan qatorlar: yo`q
  qisman tarmoqlanish: 0
  ⭐ har xato yo'li va shohobcha ma'noli assert bilan qamraldi

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


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

Noto'g'ri fikr To'g'risi
"100% coverage = xatosiz kod" Faqat qator bajarilgan, natija tekshirilmagan bo'lishi mumkin
"Coverage — sifat metrikasi" Ko'r nuqtalarni topish vositasi
"Statement coverage yetarli" Branch coverage ishonchliroq
"Foiz qancha yuqori — shuncha yaxshi" Ma'noli testlar muhimroq
"Coverage past — kod yomon" Muhim qismlar qamralgani muhim
"assert siz test foydasiz" To'g'ri — coverage oshiradi, xato ushlamaydi
"pragma: no cover — qulay" Qamrovni yasama oshiradi, ehtiyot bo'ling
"Coverage o'zi xatolarni topadi" Faqat qamralmagan qatorlarni ko'rsatadi

6. Keng tarqalgan xatolar va yechimlari

1. Coverage ni sifat metrikasi deb bilish

python
# 100% coverage, lekin assert yo'q                 # ❌
def test_x(): funksiya(5)
def test_x(): assert funksiya(5) == 10             # ✅ natijani tekshiring

2. Faqat "baxtli yo'l" ni test qilish

python
def test_bol(): assert bol(6, 2) == 3              # ❌ nolga bo'lish qamralmagan
def test_bol_nol():                                # ✅ xato yo'lini ham
    with pytest.raises(ValueError): bol(1, 0)

3. Statement coverage ga ishonish

bash
coverage run -m pytest                             # ❌ tarmoqlanish ko'rinmaydi
coverage run --branch -m pytest                    # ✅ ikkala tomon

4. pragma: no cover ni suiiste'mol

python
def muhim(x):
    if x < 0:  # pragma: no cover                  # ❌ muhim shartni yashirish
        return xato()
# ✅ faqat haqiqatan test qilib bo'lmaydigan kod

5. Foizni quvish

python
# oxirgi 5% uchun ma'nosiz testlar yozish          # ❌
# ✅ muhim yo'llarni to'liq qamrash, arzimasni qoldirish

6. Coverage ni ko'rmaslik

bash
coverage run -m pytest                             # ❌ hisobotsiz — foydasiz
coverage report -m                                 # ✅ missing qatorlarni ko'ring

7. Test faylini coverage ga qo'shish

toml
[tool.coverage.run]
# source aniqlanmagan → testlar ham hisoblanadi    # ❌
source = ["mening_paket"]                          # ✅ faqat manba kod

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 17.1–17.6-darslar (o'tilgan): testlar — coverage ular qamrovini o'lchaydi
  • 17.8-dars: TDD — test avval yozilsa coverage tabiiy yuqori
  • 18.6-dars: pre-commit — coverage tekshiruvi
  • 28.7–28.8-darslar: CI/CD — fail_under bilan minimal qamrov
  • 31-qism: karyera — coverage sifat madaniyatining bir qismi

8. Eng yaxshi amaliyotlar

  1. Coverage ni ko'r nuqtalarni topish uchun ishlating, sifat metrikasi sifatida emas.

  2. missing qatorlarni ko'rib, yetishmayotgan testlarni yozing.

  3. Branch coverage (--branch) ishlating — ishonchliroq.

  4. Xato yo'llarini (except, raise) alohida qamrang — ko'p unutiladi.

  5. Yuqori foizni emas, muhim yo'llarni ma'noli qamrashni maqsad qiling.

  6. source ni aniqlang — faqat manba kodni o'lchang.

  7. CI da fail_under bilan minimal qamrovni majburiy qiling.

  8. pragma: no cover ni faqat haqiqatan test qilib bo'lmaydigan kodga.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

Quyidagi kod va test uchun missing qatorlarni ayting:

python
# kod.py (qatorlar raqami bilan)
1  def bel(son):
2      if son < 0:
3          return "manfiy"
4      if son == 0:
5          return "nol"
6      return "musbat"

# test.py
def test_manfiy(): assert bel(-1) == "manfiy"
def test_musbat(): assert bel(5) == "musbat"
  1. Qaysi qatorlar bajariladi?
  2. Qaysi qator missing?
  3. Statement coverage foizi (6 qatordan)?
  4. test_nol() qo'shilsa, coverage qanday o'zgaradi?

Va tushunchalar:

  1. coverage run -m pytest keyin qaysi buyruq missing ni ko'rsatadi?
  2. --branch nima qo'shadi?
  3. assert x is not None coverage ni oshiradimi?
  4. 100% coverage xatosizlikni kafolatlaydimi?
Javoblar
  1. 1, 2, 3 (manfiy uchun), 1, 2, 4, 6 (musbat uchun) → 1, 2, 3, 4, 6 bajariladi
  2. 5-qator (return "nol") — test_nol yo'q
  3. 5/6 ≈ 83%
  4. 100% (5-qator ham qamraladi)
  5. coverage report -m
  6. Har if ning ikkala tomonini (True/False) tekshiradi
  7. Ha — qator bajariladi, lekin xatoni ushlamasligi mumkin
  8. Yo'q — faqat qator bajarilganini ko'rsatadi

Vazifa 2: Xatolarni tuzating

python
1.  def test_narx():
        yakuniy_narx(100, 20)               # coverage 100%, lekin...

2.  # faqat statement coverage
    # coverage run -m pytest

3.  def test_bol():
        assert bol(6, 2) == 3               # nolga bo'lish qamralmagan

4.  def muhim_hisob(x):
        if x < 0:  # pragma: no cover       # muhim shart yashirilgan
            raise ValueError()
        return x * 2

5.  [tool.coverage.run]
    # source yo'q                           # testlar ham hisoblanadi
Javoblar
python
1.  def test_narx():
        assert yakuniy_narx(100, 20) == 80  # natijani tekshiring

2.  # branch coverage
    # coverage run --branch -m pytest

3.  def test_bol():
        assert bol(6, 2) == 3
    def test_bol_nol():                     # xato yo'lini qamrang
        with pytest.raises(ValueError):
            bol(1, 0)

4.  def muhim_hisob(x):
        if x < 0:                           # pragma olib tashlandi
            raise ValueError()
        return x * 2
    # test:
    def test_manfiy():
        with pytest.raises(ValueError):
            muhim_hisob(-1)

5.  [tool.coverage.run]
    source = ["mening_paket"]               # faqat manba kod
    branch = true

Vazifa 3: Coverage hisobotini tahlil

Bir modul uchun coverage o'lchang va:

  1. coverage report -m va coverage html ni solishtiring
  2. Eng past qamrovli funksiyani toping
  3. Uning missing qatorlarini ma'noli test bilan yoping
  4. Branch coverage bilan qayta o'lchang

Vazifa 4: 100% coverage tuzog'i

  1. Buzuq funksiya yozing (assert siz test 100% beradi)
  2. Coverage 100% ekanini ko'rsating, lekin xato borligini isbotlang
  3. assert qo'shib xatoni ushlang
  4. Xulosa: coverage nima o'lchaydi va nima o'lchamaydi

Vazifa 5: fail_under bilan CI

  1. pyproject.toml da fail_under = 80 sozlang
  2. Qamrov 80% dan past bo'lganda coverage report xato (chiqish kodi 2) berishini ko'rsating
  3. Testlar qo'shib 80% dan oshiring
  4. Bu CI/CD (28-qism) da qanday ishlashini tushuntiring

Vazifa 6: Branch tuzog'i

Murakkab shartli funksiya (and, or, ichma-ich if):

  1. Statement coverage 100% beradigan minimal test yozing
  2. Branch coverage bilan qamralmagan tarmoqlanishlarni toping
  3. Ularni ham qamrang
  4. Statement va branch coverage farqini o'lchab ko'rsating

Vazifa 7: O'ylash

Coverage — kuchli, lekin xavfli metrika: menejerlar uni "sifat ko'rsatkichi" sifatida ishlatishga moyil ("90% coverage bo'lsin"), bu esa dasturchilarni ma'nosiz testlar yozishga undaydi (Goodhart qonuni: "metrika maqsadga aylansa, u yaxshi metrika bo'lishdan to'xtaydi"). Nima uchun coverage ni maqsad qilish teskari natija beradi, va coverage ni to'g'ri (foydali) ishlatishning yo'li qanday?

Javob

Qisqa javob: Coverage ni maqsad qilish Goodhart qonuniga tushadi: dasturchilar foizni oshirish uchun kodni ishga tushiradigan, lekin natijani tekshirmaydigan testlar yozadi. Foiz ko'tariladi, sifat esa yo'q. Coverage ni to'g'ri ishlatish — uni maqsad emas, vosita sifatida ko'rish: qaysi muhim kod test qilinmaganini topish uchun, keyin uni ma'noli test bilan yopish. Metrika o'zi yomon emas, uni maqsadga aylantirish yomon.

1. Nega coverage ni maqsad qilish yomon

Muammo Tafsilot
Goodhart qonuni Metrika maqsad bo'lsa, buziladi
Ma'nosiz testlar assert siz, faqat chaqiruv
Yolg'on xotirjamlik "90% — xavfsiz" degan noto'g'ri ishonch
Vaqt isrofi Arzimas kod uchun test yozish
Sifat emas, ko'rsatkich Testlar sonini emas, sifatini quvish kerak

2. Coverage nima o'lchaydi va nima o'lchamaydi

O'lchaydi O'lchamaydi
Qator bajarildimi Natija to'g'rimi
Tarmoqlanish qamraldimi Chegara holatlari ma'nolimi
Ko'r nuqtalar Testlarning sifati
Qamrov foizi Xatolar yo'qligi

3. To'g'ri ishlatish

Yondashuv Foyda
Ko'r nuqtalarni topish Yetishmayotgan muhim testlar
Xato yo'llarini ko'rsatish except qamralmagani
Branch coverage Shohobchalarni tekshirish
CI da minimal (fail_under) Regressiyani oldini olish
Ma'noli test + coverage Sifat va qamrov birga

4. Goodhart qonuni umumiy

Bu faqat coverage emas — har qanday metrika maqsadga aylansa buziladi:

  • Kod qatorlari soni → uzun, ortiqcha kod
  • Bajarilgan vazifalar → mayda vazifalarga bo'lish
  • Yopilgan xatolar → mayda xatolarni sun'iy yaratish

5. Amaliy qoida

  1. Coverage — diagnostika, maqsad emas
  2. Past coverage — "bu yerni tekshir" signali
  3. Yuqori coverage — kafolat emas, faqat ko'p qamrov
  4. Sifat: ma'noli assert + muhim yo'llar + xato holatlari

6. Xulosa

  1. Coverage ni maqsad qilish ma'nosiz testlarga olib keladi (Goodhart)
  2. Coverage qator bajarilganini o'lchaydi, to'g'riligini emas
  3. Uni ko'r nuqtalarni topish vositasi sifatida ishlating
  4. Ma'noli testlar + coverage = sifat

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda coverage bilan testlar qamrovini o'lchashni o'rgandik.

Eng muhim uch fikr:

  1. Coverage — ko'r nuqtalarni topish vositasi. coverage run -m pytest va coverage report -m testlar kodning qaysi qatorlarini ishga tushirganini ko'rsatadi; missing ustuni aynan qaysi qatorlar test qilinmaganini aytadi. Bu ko'r nuqtalar ko'pincha xato yo'llari (except, raise), chegara shartlari va kam ishlatiladigan funksiyalar — "baxtli yo'l" test qilinib, ular unutiladi.

  2. Branch coverage statement coverage dan ishonchliroq. Oddiy statement coverage if x: bajarilganini belgilaydi, lekin x False bo'lgan holat qamralmagan bo'lishi mumkin; coverage run --branch har shartning ikkala tomonini talab qiladi va qisman qamralgan (BrPart) tarmoqlanishlarni ko'rsatadi. pytest-cov va pyproject.toml dagi fail_under CI/CD da minimal qamrovni majburiy qiladi.

  3. 100% coverage ≠ xatosiz kod. Coverage faqat qator bajarilganini o'lchaydi, to'g'ri ishlaganini emas: assert siz yoki assert x is not None kabi ma'nosiz test coverage ni oshiradi, lekin xatoni ushlamaydi. Coverage ni maqsad qilish Goodhart qonuniga tushadi — foiz uchun ma'nosiz test yoziladi. Uni sifat metrikasi emas, diagnostika sifatida ishlating: muhim yo'llarni ma'noli testlar bilan qamrang, foizni quvmang.

Keyingi darsda TDD (Test-Driven Development) ni o'rganamiz: testni koddan oldin yozish falsafasini — qizil (test yiqiladi) → yashil (kod yoziladi) → refaktoring siklini va u dizaynga qanday ta'sir qilishini.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.7-dars: Coverage — IlmHamroh