14-bo‘lim

Qoplama va uning chegaralari

Qoplamani o'lchash, qator va shox qoplamasi farqi, 100% qoplamadagi xato, mutatsion sinov va qoplama raqamini maqsadga aylantirish xavfi.

🕑 18 daqiqa o‘qish 📄 802 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. O'lchashni boshlash
  2. 100% qoplama va yashirin xato
  3. Qator va shox qoplamasi
  4. Mutatsion sinov: qoplamani tekshirish
  5. Qoplama raqami maqsadga aylanganda
  6. Nimani hisobdan chiqarish kerak
  7. Xulosa

Qoplama (coverage) - testlar ishga tushirganda kodning qaysi qismlari bajarilganini ko'rsatadigan o'lchov.

U juda foydali vosita. Va u juda ko'p noto'g'ri tushuniladi.

O'lchashni boshlash #

Terminal
pip install pytest-cov
Natija
Collecting pytest-cov
  Downloading pytest_cov-7.1.0-py3-none-any.whl.metadata (32 kB)
Collecting coverage>=7.10.6 (from coverage[toml]>=7.10.6->pytest-cov)
  Downloading coverage-7.16.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (8.2 kB)
Requirement already satisfied: pluggy>=1.2 in ./.venv/lib/python3.13/site-packages (from pytest-cov) (1.6.0)
Requirement already satisfied: pytest>=7 in ./.venv/lib/python3.13/site-packages (from pytest-cov) (9.1.1)
Requirement already satisfied: iniconfig>=1.0.1 in ./.venv/lib/python3.13/site-packages (from pytest>=7->pytest-cov) (2.3.0)
Requirement already satisfied: packaging>=22 in ./.venv/lib/python3.13/site-packages (from pytest>=7->pytest-cov) (26.3)
Requirement already satisfied: pygments>=2.7.2 in ./.venv/lib/python3.13/site-packages (from pytest>=7->pytest-cov) (2.21.0)
Downloading pytest_cov-7.1.0-py3-none-any.whl (22 kB)
Downloading coverage-7.16.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (257 kB)
Installing collected packages: coverage, pytest-cov

Successfully installed coverage-7.16.1 pytest-cov-7.1.0
Python
def yetkazish_narxi(summa, shahar):
    """500 000 so'mdan katta xaridda yetkazish bepul."""
    narx = 20_000
    if summa > 500_000:
        narx = 0
    if shahar != "Toshkent":
        narx += 15_000
    return narx
Python
from yetkazish import yetkazish_narxi


def test_toshkentda_katta_xarid_bepul():
    assert yetkazish_narxi(600_000, "Toshkent") == 0


def test_boshqa_shaharda_kichik_xarid():
    assert yetkazish_narxi(100_000, "Samarqand") == 35_000
Terminal
pytest -q --cov=yetkazish --cov-report=term-missing test_yetkazish.py
Natija
..                                                               [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name           Stmts   Miss  Cover   Missing
--------------------------------------------
yetkazish.py       7      0   100%
--------------------------------------------
TOTAL              7      0   100%
2 passed in 0.02s

100%. Hamma qator bajarildi, ikkala shart ham rost va yolg'on tomonidan sinaldi.

Endi diqqat bilan qarang - kodda xato bor.

100% qoplama va yashirin xato #

Qoida shunday edi: 500 000 so'mdan katta xaridda yetkazish bepul. Samarqandga 600 000 so'mlik buyurtma bersak nima bo'ladi?

Python
from yetkazish import yetkazish_narxi


def test_toshkentda_katta_xarid_bepul():
    assert yetkazish_narxi(600_000, "Toshkent") == 0


def test_boshqa_shaharda_kichik_xarid():
    assert yetkazish_narxi(100_000, "Samarqand") == 35_000


def test_boshqa_shaharda_katta_xarid_ham_bepul():
    assert yetkazish_narxi(600_000, "Samarqand") == 0
Terminal
pytest -q --tb=line --cov=yetkazish test_yetkazish.py
Natija
..F                                                              [100%]
=============================== FAILURES ===============================
E   AssertionError: assert 15000 == 0
     +  where 15000 = yetkazish_narxi(600000, 'Samarqand')
/home/dasturchi/loyiha/test_yetkazish.py:13: AssertionError: assert 15000 == 0
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name           Stmts   Miss  Cover
----------------------------------
yetkazish.py       7      0   100%
----------------------------------
TOTAL              7      0   100%
======================= short test summary info ========================
FAILED test_yetkazish.py::test_boshqa_shaharda_katta_xarid_ham_bepul
1 failed, 2 passed in 0.03s

Mana xato: mijoz 600 000 so'mlik buyurtma bergan, lekin undan 15 000 so'm olinmoqda. Va bu qoplama 100% bo'lgan holda sodir bo'ldi.

Qoplama qatorlarni sanaydi, holatlarni emas

Bizning kodimizda ikkita shart bor - demak to'rtta kombinatsiya:

SummaShaharSinaldimi
KattaToshkentHa
KichikBoshqaHa
KichikToshkentYo'q
KattaBoshqaYo'q - xato shu yerda

Ikkita test to'rtta holatdan ikkitasini qamradi va barcha qatorlarni bajardi. Qoplama 100% ko'rsatdi.

Qoida: qoplama testlar nimani bajarganini ko'rsatadi, nimani tekshirganini emas.

Kodni tuzatamiz:

Python
def yetkazish_narxi(summa, shahar):
    """500 000 so'mdan katta xaridda yetkazish bepul."""
    if summa > 500_000:
        return 0
    narx = 20_000
    if shahar != "Toshkent":
        narx += 15_000
    return narx
Terminal
pytest -q --cov=yetkazish --cov-report=term-missing test_yetkazish.py
Natija
...                                                              [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name           Stmts   Miss  Cover   Missing
--------------------------------------------
yetkazish.py       7      0   100%
--------------------------------------------
TOTAL              7      0   100%
3 passed in 0.02s

Qator va shox qoplamasi #

Ikki xil o'lchov bor va ular sezilarli farq qiladi:

Bir xil test, ikki xil o'lchov Qator qoplamasi if shart: ish_bajar() Shart rost bo'lgan bitta test ikkala qatorni ham bajaradi Natija: 100% Shox qoplamasi if shart: -> rost -> yolg'on Ikkala yo'nalish ham alohida hisoblanadi Natija: to'liq emas Shox qoplamasini har doim yoqing: --cov-branch - u bepul va ancha ko'proq narsani ko'rsatadi
Bir xil kod, bir xil test - ikki xil raqam

Buni amalda ko'ramiz:

Python
def chegirma(summa, talaba):
    if talaba:
        summa = summa * 0.5
    return round(summa)
Python
from chegirma import chegirma


def test_talabaga_yarim_narx():
    assert chegirma(100_000, True) == 50_000
Terminal
pytest -q --cov=chegirma --cov-report=term-missing test_chegirma.py
Natija
.                                                                [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name          Stmts   Miss  Cover   Missing
-------------------------------------------
chegirma.py       4      0   100%
-------------------------------------------
TOTAL             4      0   100%
1 passed in 0.02s

Endi shox qoplamasi bilan:

Terminal
pytest -q --cov=chegirma --cov-branch --cov-report=term-missing test_chegirma.py
Natija
.                                                                [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------
chegirma.py       4      0      2      1    83%   2->4
---------------------------------------------------------
TOTAL             4      0      2      1    83%
1 passed in 0.02s

Bitta bayroq yangi ma'lumot berdi. Missing ustunidagi 2->4 yozuvi shuni bildiradi: 2-qatordan 4-qatorga o'tish hech qachon sodir bo'lmagan - ya'ni talaba yolg'on bo'lgan yo'l sinalmagan.

Qator qoplamasi hamon 100% ko'rsatardi, shox qoplamasi esa teshikni topdi.

Mutatsion sinov: qoplamani tekshirish #

Qoplama testlarning borligini o'lchaydi, kuchini emas. Testlaringiz haqiqatan ish beradimi - buni bilishning ishonchli yo'li kodni ataylab buzish va testlar buni sezishini kuzatish.

Bu usul mutatsion sinov (mutation testing) deb ataladi. Uni qo'lda ham qilish mumkin:

Python
def chegirma(summa, talaba):
    if not talaba:                 # mutatsiya: shart teskari qilindi
        summa = summa * 0.5
    return round(summa)
Terminal
pytest -q --tb=no --cov=chegirma test_chegirma.py
Natija
F                                                                [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name          Stmts   Miss  Cover
---------------------------------
chegirma.py       4      1    75%
---------------------------------
TOTAL             4      1    75%
======================= short test summary info ========================
FAILED test_chegirma.py::test_talabaga_yarim_narx - assert 100000 == ...
1 failed in 0.02s

Test yiqildi - demak u haqiqatan ishlaydi. Endi boshqa mutatsiya:

Python
def chegirma(summa, talaba):
    if talaba:
        summa = summa * 0.5
    return round(summa + 0.4)      # mutatsiya: kichik qo'shimcha
Terminal
pytest -q --tb=no --cov=chegirma test_chegirma.py
Natija
.                                                                [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name          Stmts   Miss  Cover
---------------------------------
chegirma.py       4      0   100%
---------------------------------
TOTAL             4      0   100%
1 passed in 0.02s

Bu safar test sezmadi. round(50000.4) ham 50000 ga teng - ya'ni testimiz yumaloqlash mantig'ini umuman tekshirmaydi.

Mana shu - qoplama ko'rsatolmaydigan ma'lumot. Har bir omon qolgan mutatsiya - testlaringizdagi teshik.

Mutatsion sinovni avtomatlashtirish

Python da mutmut va cosmic-ray kutubxonalari shu ishni o'zi qiladi: kodingizga yuzlab kichik o'zgarish kiritadi va har biri uchun testlarni ishga tushiradi.

Natija "mutatsion ball" ko'rinishida beriladi: nechta mutatsiya o'ldirilgan (test tutgan) va nechtasi omon qolgan.

Bu qoplamadan ancha ishonchli o'lchov. Kamchiligi - sekinligi: testlar yuzlab marta ishga tushiriladi. Shuning uchun uni odatda kodning eng muhim qismlari uchun, haftada bir marta ishlatishadi.

Kodni qaytaramiz:

Python
def chegirma(summa, talaba):
    if talaba:
        summa = summa * 0.5
    return round(summa)

Qoplama raqami maqsadga aylanganda #

"Qoplama 90% dan kam bo'lmasin" qoidasi nima keltiradi Kutilgan natija • Ko'proq foydali test • Kam xato • Ishonchli refaktoring • Yaxshi uyqu Haqiqiy natija • assert siz "test"lar • Getter'larga testlar • Fayllarni hisobdan chiqarish • Raqam 91%, ishonch o'sha-o'sha O'lchov maqsadga aylanganda, u o'lchov bo'lishdan to'xtaydi
Qoplama - termometr; uni isitib, kasallikni davolab bo'lmaydi

Eng yomon holat - assert siz yozilgan "test":

Python
from yetkazish import yetkazish_narxi


def test_yetkazish_ishlaydi():
    yetkazish_narxi(600_000, "Toshkent")
    yetkazish_narxi(100_000, "Samarqand")
Terminal
pytest -q --cov=yetkazish --cov-report=term-missing test_qopqoq.py
Natija
.                                                                [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name           Stmts   Miss  Cover   Missing
--------------------------------------------
yetkazish.py       7      0   100%
--------------------------------------------
TOTAL              7      0   100%
1 passed in 0.02s

Qoplama 100%, tekshirilgan narsa - nol. Funksiya nima qaytarsa ham bu test o'tadi.

Qoplamani qanday to'g'ri ishlatish kerak

Uch qoida yetarli:

  1. Qoplamani yiqilgan raqam sifatida emas, ro'yxat sifatida o'qing. --cov-report=term-missing qaysi qatorlar sinalmaganini ko'rsatadi. O'sha ro'yxatni ko'rib chiqing: bu qator haqiqatan kerakmi, kerak bo'lsa nima uchun testi yo'q?
  2. Qoplamaning tushishini kuzating, darajasini emas. Yangi kod qoplamasiz kelayotgan bo'lsa - bu signal. 87% dan 86% ga tushish esa hech narsani anglatmaydi.
  3. Nol qoplamali fayllarni qidiring. Odatda eng katta xavf shu yerda: butun modul hech qachon sinalmagan.

Nimani hisobdan chiqarish kerak #

Ba'zi kod qoplamaga kirmasligi kerak - u sizni chalg'itadi:

INI
[run]
branch = True
omit =
    */.venv/*
    test_*.py
    */migrations/*

[report]
exclude_lines =
    pragma: no cover
    if __name__ == .__main__.:
    raise NotImplementedError
Terminal
pytest -q --cov=. --cov-report=term test_yetkazish.py
Natija
...                                                              [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________

Name           Stmts   Miss Branch BrPart  Cover
------------------------------------------------
chegirma.py        4      4      2      0     0%
yetkazish.py       7      0      4      1    91%
------------------------------------------------
TOTAL             11      4      6      1    59%
3 passed in 0.03s

Sozlama fayli tufayli endi --cov-branch ni har safar yozish shart emas, virtual muhit va test fayllarining o'zi esa hisobga olinmaydi.

Va hisobot darhol bitta narsani ko'rsatdi: chegirma.py - 0%. Biz uni bu ishga tushirishda umuman sinamadik. Aynan shunday qatorlar - qoplama hisobotining eng qimmatli qismi.

Amaliy topshiriq
  1. pytest-cov ni o'rnatib, yetkazish.py uchun qoplamani o'lchang.
  2. Qoplama 100% ekaniga ishonch hosil qiling va keyin xatoni toping.
  3. To'rtta kombinatsiyani jadval qilib chizing va qaysilari sinalganini belgilang.
  4. Uchinchi testni qo'shib, xatoni tuting va kodni tuzating.
  5. chegirma.py uchun qator va shox qoplamasini solishtiring.
  6. Farq nechaga teng chiqdi?
  7. Shartni teskari qilib, mutatsiya testini o'zingiz o'tkazing.
  8. Testlar sezmaydigan yana bitta mutatsiya toping.
  9. assert siz test yozib, uning qoplamasini o'lchang.
  10. O'z loyihangizda nol qoplamali fayl bormi - qidirib ko'ring.

Xulosa #

  • Qoplama - testlar ishga tushganda kodning qaysi qismlari bajarilganini ko'rsatadi.
  • pytest --cov=modul --cov-report=term-missing qoplamani va sinalmagan qatorlarni ko'rsatadi.
  • 100% qoplama xatolar yo'q degani emas: ikkita shart to'rtta kombinatsiya beradi.
  • Qoplama testlar nimani bajarganini ko'rsatadi, nimani tekshirganini emas.
  • Qator qoplamasi qatorlarni, shox qoplamasi esa har if ning ikkala yo'nalishini sanaydi.
  • --cov-branch bepul va ancha ko'proq ma'lumot beradi - uni har doim yoqing.
  • Mutatsion sinov kodni ataylab buzib, testlar buni sezishini tekshiradi.
  • Omon qolgan har bir mutatsiya - testlardagi teshik; mutmut va cosmic-ray buni avtomatlashtiradi.
  • assert siz yozilgan "test" 100% qoplama beradi va hech narsani tekshirmaydi.
  • Qoplama raqami maqsadga aylansa, u o'lchov bo'lishdan to'xtaydi.
  • Qoplamani ro'yxat sifatida o'qing: term-missing dagi qatorlarni ko'rib chiqing.
  • Darajani emas, tushishni kuzating; nol qoplamali fayllarni alohida qidiring.
  • .coveragerc fayli sozlamalarni bir joyda saqlaydi va keraksiz papkalarni hisobdan chiqaradi.

Keyingi bo'limda testlarning eng asabiy turiga o'tamiz: goh o'tib, goh yiqiladiganlariga.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.