20-bo‘lim
Yakuniy loyiha - testi yo'q loyihaga to'liq to'plam
Bitta testi yo'q xarajat hisobchisini olib, unga piramida bo'yicha modul, integratsiya, API va dublyor testlarini qurish, qoplamani yopish va CI ga ulash.
Ushbu bo‘lim mundarijasi
Endi hamma narsani birlashtiramiz. Bizda kichik loyiha bor - xarajat hisobchisi - va unda bitta ham test yo'q.
Vazifa: unga ishonchli test to'plami qurish. Har qadamda oldingi bo'limlardagi qoidalarni qo'llaymiz.
Loyiha bilan tanishish #
Uchta modul: qoidalar, ombor va veb interfeys.
from datetime import date
TURKUMLAR = ("oziq-ovqat", "transport", "uy", "salomatlik", "boshqa")
OYLIK_CHEGARA = 3_000_000
def xarajat_yarat(nom, summa, turkum, sana):
"""Bitta xarajat yozuvini tekshirib yaratadi."""
if not nom or not nom.strip():
raise ValueError("nom bo'sh bo'lishi mumkin emas")
if summa <= 0:
raise ValueError("summa musbat bo'lishi kerak")
if turkum not in TURKUMLAR:
raise ValueError(f"noma'lum turkum: {turkum}")
if not isinstance(sana, date):
raise TypeError("sana date turida bo'lishi kerak")
return {"nom": nom.strip(), "summa": summa, "turkum": turkum, "sana": sana}
def oylik_jami(xarajatlar, yil, oy):
"""Berilgan oydagi xarajatlar yig'indisi."""
return sum(
x["summa"] for x in xarajatlar
if x["sana"].year == yil and x["sana"].month == oy
)
def turkum_boyicha(xarajatlar):
"""Turkumlar kesimida yig'indi; bo'sh turkumlar qaytarilmaydi."""
natija = {}
for xarajat in xarajatlar:
natija[xarajat["turkum"]] = natija.get(xarajat["turkum"], 0) + xarajat["summa"]
return natija
def ogohlantirish_darajasi(jami, chegara=OYLIK_CHEGARA):
"""Chegaraga nisbatan holat: yashil, sariq yoki qizil."""
if chegara <= 0:
raise ValueError("chegara musbat bo'lishi kerak")
foiz = jami / chegara * 100
if foiz >= 100:
return "qizil"
if foiz >= 70:
return "sariq"
return "yashil"
import sqlite3
from datetime import date
SXEMA = """
CREATE TABLE xarajat (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
summa INTEGER NOT NULL,
turkum TEXT NOT NULL,
sana TEXT NOT NULL
);
"""
def ulanish_och(yol):
ulanish = sqlite3.connect(yol)
ulanish.row_factory = sqlite3.Row
return ulanish
def sxemani_yarat(ulanish):
ulanish.executescript(SXEMA)
def saqla(ulanish, xarajat):
kursor = ulanish.execute(
"INSERT INTO xarajat (nom, summa, turkum, sana) VALUES (?, ?, ?, ?)",
(xarajat["nom"], xarajat["summa"], xarajat["turkum"], xarajat["sana"].isoformat()),
)
ulanish.commit()
return kursor.lastrowid
def hammasi(ulanish):
qatorlar = ulanish.execute(
"SELECT nom, summa, turkum, sana FROM xarajat ORDER BY sana, id"
).fetchall()
return [
{
"nom": q["nom"],
"summa": q["summa"],
"turkum": q["turkum"],
"sana": date.fromisoformat(q["sana"]),
}
for q in qatorlar
]
class SmsXabarchi:
"""Haqiqiy xizmat - testda hech qachon chaqirilmasligi kerak."""
def ogohlantir(self, matn):
raise RuntimeError("haqiqiy SMS yuborildi")
def oy_yakunini_xabar_qil(xabarchi, jami, chegara, daraja):
"""Faqat sariq va qizil darajada xabar yuboradi."""
if daraja == "yashil":
return False
xabarchi.ogohlantir(
f"Oylik xarajat {jami} so'm ({chegara} so'mdan {daraja} daraja)"
)
return True
Reja #
Piramida bo'yicha yuramiz - pastdan yuqoriga:
1-qadam: modul testlari #
Avval xarajat_yarat ning xato yo'llari. Ular bir xil shaklda
bo'lgani uchun parametrlaymiz (6-bo'lim):
from datetime import date
import pytest
from qoidalar import xarajat_yarat
SANA = date(2026, 3, 15)
def test_togri_xarajat_yaratiladi():
xarajat = xarajat_yarat(" Non ", 12_000, "oziq-ovqat", SANA)
assert xarajat == {
"nom": "Non",
"summa": 12_000,
"turkum": "oziq-ovqat",
"sana": SANA,
}
@pytest.mark.parametrize("nom, summa, turkum, kutilgan_xato", [
("", 1000, "uy", "nom bo'sh"),
(" ", 1000, "uy", "nom bo'sh"),
("Non", 0, "uy", "musbat"),
("Non", -5, "uy", "musbat"),
("Non", 1000, "sayohat", "noma'lum turkum"),
])
def test_notogri_xarajat_xato_beradi(nom, summa, turkum, kutilgan_xato):
with pytest.raises(ValueError, match=kutilgan_xato):
xarajat_yarat(nom, summa, turkum, SANA)
def test_sana_notogri_turda():
with pytest.raises(TypeError, match="date turida"):
xarajat_yarat("Non", 1000, "uy", "2026-03-15")
pytest -q -p no:randomly test_qoidalar.py
....... [100%]
7 passed in 0.01s
Endi eng muhim qism - chegara qiymatlari. ogohlantirish_darajasi
da ikkita chegara bor (70% va 100%), demak har biri uchun uchtadan
qiymat sinaladi:
import pytest
from qoidalar import ogohlantirish_darajasi
CHEGARA = 1_000_000
@pytest.mark.parametrize("jami, kutilgan", [
(0, "yashil"), # eng past
(699_999, "yashil"), # sariqdan bir tiyin past
(700_000, "sariq"), # aynan 70%
(700_001, "sariq"),
(999_999, "sariq"), # qizildan bir tiyin past
(1_000_000, "qizil"), # aynan 100%
(1_000_001, "qizil"),
(50_000_000, "qizil"), # juda katta
])
def test_daraja_chegaralari(jami, kutilgan):
assert ogohlantirish_darajasi(jami, CHEGARA) == kutilgan
def test_nol_chegara_xato_beradi():
with pytest.raises(ValueError, match="musbat"):
ogohlantirish_darajasi(100, 0)
pytest -q -p no:randomly test_darajalar.py
......... [100%]
9 passed in 0.01s
O'nta holat - va ularning to'rttasi aynan chegarada. Agar kimdir
>= ni > ga almashtirsa, test darhol aytadi.
Qolgan ikkita funksiya uchun ham testlar yozamiz. Ularga fixture qo'l keladi (7-bo'lim):
from datetime import date
import pytest
from qoidalar import xarajat_yarat
@pytest.fixture
def xarajat_yasa():
"""Fabrika: testda faqat muhim maydonlar ko'rsatiladi."""
def yasovchi(nom="Non", summa=10_000, turkum="oziq-ovqat", sana=date(2026, 3, 15)):
return xarajat_yarat(nom, summa, turkum, sana)
return yasovchi
@pytest.fixture
def mart_xarajatlari(xarajat_yasa):
return [
xarajat_yasa(nom="Non", summa=12_000, sana=date(2026, 3, 1)),
xarajat_yasa(nom="Metro", summa=2_000, turkum="transport", sana=date(2026, 3, 2)),
xarajat_yasa(nom="Kommunal", summa=450_000, turkum="uy", sana=date(2026, 3, 5)),
xarajat_yasa(nom="Sut", summa=18_000, sana=date(2026, 3, 20)),
xarajat_yasa(nom="Fevral noni", summa=9_000, sana=date(2026, 2, 28)),
]
from qoidalar import oylik_jami, turkum_boyicha
def test_bosh_royxat_jamisi_nol():
assert oylik_jami([], 2026, 3) == 0
def test_faqat_kerakli_oy_hisoblanadi(mart_xarajatlari):
assert oylik_jami(mart_xarajatlari, 2026, 3) == 482_000
def test_boshqa_oy_alohida(mart_xarajatlari):
assert oylik_jami(mart_xarajatlari, 2026, 2) == 9_000
def test_mavjud_bolmagan_oy(mart_xarajatlari):
assert oylik_jami(mart_xarajatlari, 2026, 7) == 0
def test_turkumlar_jamlanadi(mart_xarajatlari):
assert turkum_boyicha(mart_xarajatlari) == {
"oziq-ovqat": 39_000,
"transport": 2_000,
"uy": 450_000,
}
def test_bosh_turkumlar_qaytarilmaydi(xarajat_yasa):
natija = turkum_boyicha([xarajat_yasa(turkum="transport")])
assert list(natija) == ["transport"]
pytest -q -p no:randomly test_jamlash.py
...... [100%]
6 passed in 0.01s
2-qadam: dublyor bilan xabarnoma #
oy_yakunini_xabar_qil tashqi xizmatni chaqiradi. Uni spy bilan
almashtiramiz (8-bo'lim):
import pytest
from xabarchi import oy_yakunini_xabar_qil
class SoxtaXabarchi:
def __init__(self):
self.xabarlar = []
def ogohlantir(self, matn):
self.xabarlar.append(matn)
@pytest.fixture
def xabarchi():
return SoxtaXabarchi()
def test_yashil_darajada_xabar_ketmaydi(xabarchi):
yuborildi = oy_yakunini_xabar_qil(xabarchi, 100_000, 1_000_000, "yashil")
assert yuborildi is False
assert xabarchi.xabarlar == []
@pytest.mark.parametrize("daraja", ["sariq", "qizil"])
def test_sariq_va_qizil_darajada_xabar_ketadi(xabarchi, daraja):
yuborildi = oy_yakunini_xabar_qil(xabarchi, 900_000, 1_000_000, daraja)
assert yuborildi is True
assert len(xabarchi.xabarlar) == 1
def test_xabar_matnida_summa_bor(xabarchi):
oy_yakunini_xabar_qil(xabarchi, 900_000, 1_000_000, "sariq")
assert "900000" in xabarchi.xabarlar[0]
assert "sariq" in xabarchi.xabarlar[0]
pytest -q -p no:randomly test_xabarchi.py
.... [100%]
4 passed in 0.01s
Diqqat: SmsXabarchi hech qachon yaratilmadi. Agar kod tasodifan
haqiqiy xizmatni chaqirsa, u RuntimeError tashlaydi va test
yiqiladi - bu ataylab qo'yilgan tuzoq.
3-qadam: integratsiya testlari #
Endi haqiqiy SQLite bilan (11-bo'lim). Fixture conftest.py ga
qo'shiladi:
from datetime import date
import pytest
from ombor import sxemani_yarat, ulanish_och
from qoidalar import xarajat_yarat
@pytest.fixture
def xarajat_yasa():
"""Fabrika: testda faqat muhim maydonlar ko'rsatiladi."""
def yasovchi(nom="Non", summa=10_000, turkum="oziq-ovqat", sana=date(2026, 3, 15)):
return xarajat_yarat(nom, summa, turkum, sana)
return yasovchi
@pytest.fixture
def mart_xarajatlari(xarajat_yasa):
return [
xarajat_yasa(nom="Non", summa=12_000, sana=date(2026, 3, 1)),
xarajat_yasa(nom="Metro", summa=2_000, turkum="transport", sana=date(2026, 3, 2)),
xarajat_yasa(nom="Kommunal", summa=450_000, turkum="uy", sana=date(2026, 3, 5)),
xarajat_yasa(nom="Sut", summa=18_000, sana=date(2026, 3, 20)),
xarajat_yasa(nom="Fevral noni", summa=9_000, sana=date(2026, 2, 28)),
]
@pytest.fixture
def ulanish():
aloqa = ulanish_och(":memory:")
sxemani_yarat(aloqa)
yield aloqa
aloqa.close()
from datetime import date
from ombor import hammasi, saqla
from qoidalar import oylik_jami
def test_yangi_baza_bosh(ulanish):
assert hammasi(ulanish) == []
def test_saqlangan_xarajat_ozi_qaytadi(ulanish, xarajat_yasa):
xarajat = xarajat_yasa(nom="Non", summa=12_000)
saqla(ulanish, xarajat)
assert hammasi(ulanish) == [xarajat]
def test_sana_turi_saqlanadi(ulanish, xarajat_yasa):
saqla(ulanish, xarajat_yasa(sana=date(2026, 3, 15)))
olingan = hammasi(ulanish)[0]
assert olingan["sana"] == date(2026, 3, 15)
assert isinstance(olingan["sana"], date)
def test_natija_sana_boyicha_tartiblanadi(ulanish, mart_xarajatlari):
for xarajat in mart_xarajatlari:
saqla(ulanish, xarajat)
sanalar = [x["sana"] for x in hammasi(ulanish)]
assert sanalar == sorted(sanalar)
def test_bazadan_oqib_jami_hisoblanadi(ulanish, mart_xarajatlari):
for xarajat in mart_xarajatlari:
saqla(ulanish, xarajat)
assert oylik_jami(hammasi(ulanish), 2026, 3) == 482_000
pytest -q -p no:randomly test_ombor.py
..... [100%]
5 passed in 0.01s
Uchinchi test alohida muhim: SQLite sanani matn sifatida
saqlaydi, shuning uchun uni date ga qaytarish kodda qilinadi.
Bu aynan modul testi ko'ra olmaydigan xato turi.
4-qadam: API testlari #
from datetime import date
from flask import Flask, jsonify, request
from ombor import hammasi, saqla, sxemani_yarat, ulanish_och
from qoidalar import OYLIK_CHEGARA, ogohlantirish_darajasi, oylik_jami, xarajat_yarat
def ilova_yarat(ulanish=None):
app = Flask(__name__)
if ulanish is None:
ulanish = ulanish_och(":memory:")
sxemani_yarat(ulanish)
app.config["ULANISH"] = ulanish
@app.get("/xarajatlar")
def royxat():
xarajatlar = hammasi(app.config["ULANISH"])
return jsonify([
{**x, "sana": x["sana"].isoformat()} for x in xarajatlar
])
@app.post("/xarajatlar")
def qosh():
malumot = request.get_json(silent=True) or {}
try:
xarajat = xarajat_yarat(
malumot.get("nom", ""),
malumot.get("summa", 0),
malumot.get("turkum", ""),
date.fromisoformat(malumot.get("sana", "")),
)
except ValueError as xato:
return jsonify({"xato": str(xato)}), 400
yozuv_id = saqla(app.config["ULANISH"], xarajat)
return jsonify({"id": yozuv_id}), 201
@app.get("/hisobot/<int:yil>/<int:oy>")
def hisobot(yil, oy):
jami = oylik_jami(hammasi(app.config["ULANISH"]), yil, oy)
return jsonify({
"yil": yil,
"oy": oy,
"jami": jami,
"daraja": ogohlantirish_darajasi(jami, OYLIK_CHEGARA),
})
return app
pip install flask
Collecting flask
Downloading flask-3.1.3-py3-none-any.whl.metadata (3.2 kB)
Collecting blinker>=1.9.0 (from flask)
Downloading blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB)
Collecting click>=8.1.3 (from flask)
Downloading click-8.5.0-py3-none-any.whl.metadata (2.6 kB)
Collecting itsdangerous>=2.2.0 (from flask)
Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
Collecting jinja2>=3.1.2 (from flask)
Downloading jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB)
Collecting markupsafe>=2.1.1 (from flask)
Downloading markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.7 kB)
Collecting werkzeug>=3.1.0 (from flask)
Downloading werkzeug-3.1.8-py3-none-any.whl.metadata (4.0 kB)
Downloading flask-3.1.3-py3-none-any.whl (103 kB)
Downloading blinker-1.9.0-py3-none-any.whl (8.5 kB)
Downloading click-8.5.0-py3-none-any.whl (125 kB)
Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Downloading jinja2-3.1.6-py3-none-any.whl (134 kB)
Downloading markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (22 kB)
Downloading werkzeug-3.1.8-py3-none-any.whl (226 kB)
Installing collected packages: markupsafe, itsdangerous, click, blinker, werkzeug, jinja2, flask
Successfully installed blinker-1.9.0 click-8.5.0 flask-3.1.3 itsdangerous-2.2.0 jinja2-3.1.6 markupsafe-3.0.3 werkzeug-3.1.8
Test mijozi uchun fixture qo'shamiz (12-bo'lim):
import pytest
from ilova import ilova_yarat
@pytest.fixture
def mijoz(ulanish):
app = ilova_yarat(ulanish)
app.config["TESTING"] = True
with app.test_client() as c:
yield c
def test_bosh_royxat(mijoz):
javob = mijoz.get("/xarajatlar")
assert javob.status_code == 200
assert javob.get_json() == []
def test_xarajat_qoshiladi(mijoz):
javob = mijoz.post("/xarajatlar", json={
"nom": "Non", "summa": 12_000,
"turkum": "oziq-ovqat", "sana": "2026-03-01",
})
assert javob.status_code == 201
assert javob.get_json() == {"id": 1}
def test_qoshilgan_xarajat_royxatda(mijoz):
mijoz.post("/xarajatlar", json={
"nom": "Non", "summa": 12_000,
"turkum": "oziq-ovqat", "sana": "2026-03-01",
})
javob = mijoz.get("/xarajatlar")
assert javob.get_json() == [{
"nom": "Non", "summa": 12_000,
"turkum": "oziq-ovqat", "sana": "2026-03-01",
}]
@pytest.mark.parametrize("tana", [
{},
{"nom": "", "summa": 1000, "turkum": "uy", "sana": "2026-03-01"},
{"nom": "Non", "summa": -1, "turkum": "uy", "sana": "2026-03-01"},
{"nom": "Non", "summa": 1000, "turkum": "sayohat", "sana": "2026-03-01"},
])
def test_notogri_malumot_400_beradi(mijoz, tana):
javob = mijoz.post("/xarajatlar", json=tana)
assert javob.status_code == 400
assert "xato" in javob.get_json()
def test_bosh_oy_hisoboti(mijoz):
javob = mijoz.get("/hisobot/2026/3")
assert javob.status_code == 200
assert javob.get_json() == {"yil": 2026, "oy": 3, "jami": 0, "daraja": "yashil"}
def test_hisobot_darajani_hisoblaydi(mijoz):
mijoz.post("/xarajatlar", json={
"nom": "Ijara", "summa": 2_500_000,
"turkum": "uy", "sana": "2026-03-01",
})
javob = mijoz.get("/hisobot/2026/3")
assert javob.get_json()["jami"] == 2_500_000
assert javob.get_json()["daraja"] == "sariq"
pytest -q -p no:randomly test_ilova.py
......... [100%]
9 passed in 0.13s
5-qadam: qoplamani tekshirish #
Hamma testni birga ishga tushirib, qoplamani o'lchaymiz (14-bo'lim):
pip install pytest-cov
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
[run]
branch = True
omit =
*/.venv/*
test_*.py
conftest.py
pytest -q -p no:randomly --cov=. --cov-report=term-missing
........................................ [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________
Name Stmts Miss Branch BrPart Cover Missing
---------------------------------------------------------
ilova.py 28 2 2 1 90% 12-13
ombor.py 16 0 0 0 100%
qoidalar.py 29 0 16 0 100%
xabarchi.py 8 1 2 0 90% 5
---------------------------------------------------------
TOTAL 81 3 20 1 96%
40 passed in 0.28s
Ikkita teshik ko'rindi:
xabarchi.py:5-SmsXabarchi.ogohlantir. U hech qachon chaqirilmagan va bu to'g'ri: biz uni ataylab chaqirmayapmiz.ilova.py:12-13-ilova_yaratga ulanish berilmagan hol. Testda biz har doim o'z ulanishimizni beramiz, shuning uchun sukut yo'l sinalmagan.
Ikkinchisi haqiqiy teshik: dastur ishga tushganda aynan shu qatorlar bajariladi. Uni yopish uchun bitta test yetadi - keyingi topshiriqda shuni yozasiz.
Birinchisini esa qoplamadan chiqarish to'g'riroq.
Bunday qatorlarni pragma: no cover bilan belgilash mumkin:
class SmsXabarchi:
"""Haqiqiy xizmat - testda hech qachon chaqirilmasligi kerak."""
def ogohlantir(self, matn): # pragma: no cover
raise RuntimeError("haqiqiy SMS yuborildi")
def oy_yakunini_xabar_qil(xabarchi, jami, chegara, daraja):
"""Faqat sariq va qizil darajada xabar yuboradi."""
if daraja == "yashil":
return False
xabarchi.ogohlantir(
f"Oylik xarajat {jami} so'm ({chegara} so'mdan {daraja} daraja)"
)
return True
[run]
branch = True
omit =
*/.venv/*
test_*.py
conftest.py
[report]
exclude_lines =
pragma: no cover
if __name__ == .__main__.:
pytest -q -p no:randomly --cov=. --cov-report=term-missing
........................................ [100%]
============================ tests coverage ============================
___________ coverage: platform linux, python 3.13.12-final-0 ___________
Name Stmts Miss Branch BrPart Cover Missing
---------------------------------------------------------
ilova.py 28 2 2 1 90% 12-13
ombor.py 16 0 0 0 100%
qoidalar.py 29 0 16 0 100%
xabarchi.py 6 0 2 0 100%
---------------------------------------------------------
TOTAL 79 2 20 1 97%
40 passed in 0.27s
pragma: no cover - ehtiyotkorlik bilanBu belgi qatorni qoplamadan chiqaradi, ya'ni hisobotni go'zallashtiradi. Uni faqat haqiqatan testlanmasligi kerak bo'lgan kodga qo'ying:
- ishlab chiqarishdagi tashqi xizmat chaqiruvi;
if __name__ == "__main__"bloki;- himoya uchun yozilgan, hech qachon yuz bermasligi kerak bo'lgan tarmoq.
Agar uni "test yozishga vaqtim yo'q" degan sabab bilan qo'ysangiz, qoplama hisoboti yolg'on gapira boshlaydi.
6-qadam: belgilar va CI #
Oxirgi qadam - to'plamni quvurga ulash (19-bo'lim):
[pytest]
addopts = -ra --strict-markers
markers =
integratsiya: ma'lumotlar bazasi talab qiladi
sekin: sekin ishlaydigan testlar
--strict-markers - e'lon qilinmagan belgi ishlatilsa, pytest xato
beradi. Bu imlo xatosidan himoya qiladi.
name: Testlar
on: [push, pull_request]
jobs:
testlar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: pip
- run: pip install pytest pytest-cov flask
- run: pytest --cov=. --cov-report=term-missing --junitxml=natija.xml
- if: always()
uses: actions/upload-artifact@v4
with:
name: natija
path: natija.xml
To'plamning yakuniy holati:
pytest -p no:randomly --collect-only -q | tail -3
test_xabarchi.py::test_xabar_matnida_summa_bor
40 tests collected in 0.09s
pytest -q -p no:randomly
........................................ [100%]
40 passed in 0.15s
Nimaga erishdik #
| Daraja | Testlar | Nimani qo'riqlaydi |
|---|---|---|
| Modul | Qoidalar, chegaralar, xato holatlari | Hisob-kitob mantig'i |
| Dublyor | Xabarnoma shartlari | Tashqi xizmat chaqiruvi |
| Integratsiya | SQLite bilan aylanma yo'l | SQL va tur o'girish |
| API | Holat kodlari, JSON, xatolar | Shartnoma |
Va eng muhimi - bu to'plam tez. U har kommitda ishlaydi, demak u haqiqatan foyda beradi.
Testi yo'q loyihaga bir kunda to'liq to'plam qurib bo'lmaydi. Tartib shunday:
- Eng qimmat mantiqdan boshlang - pul, ruxsat, hisob-kitob.
- Har nosozlikdan oldin uni ko'rsatadigan test yozing (1-bo'lim).
- Yangi kod testsiz kirmasin - bu eng arzon qoida.
- Qoplamaning tushishini kuzating, darajasini emas.
- Eski kodga xarakteristik test bilan yaqinlashing (16-bo'lim).
Bir necha oydan keyin to'plam o'z-o'zidan paydo bo'ladi.
- Uchta modulni yozib, modul testlarini ishga tushiring.
ogohlantirish_darajasida>=ni>ga almashtiring - nechta test yiqildi?- Yangi turkum qo'shing va unga test yozing.
- Dublyor testlariga uchinchi holat qo'shing: chegara aynan 100%.
- Integratsiya testlarida
ORDER BYni olib tashlang - qaysi test yiqiladi? - API ga
DELETE /xarajatlar/<id>qo'shing va unga to'rtta test yozing. ilova_yarat()ni ulanishsiz chaqiradigan test yozib, qolgan teshikni yoping.--cov-branchni yoqib, farqni solishtiring.- Integratsiya testlariga
integratsiyabelgisini qo'ying va-m "not integratsiya"ni sinang. - O'z loyihangizni tanlab, shu tartibda birinchi beshta testni yozing.
Xulosa #
- To'plam piramida bo'yicha pastdan yuqoriga quriladi: modul, dublyor, integratsiya, API.
- Bir xil shakldagi xato holatlari
parametrizebilan bitta testga jamlanadi. - Ikkita chegarasi bor funksiyaga oltitadan kam bo'lmagan chegara testi kerak.
- Fabrika fixture testda faqat muhim maydonni ko'rsatish imkonini beradi.
- Haqiqiy tashqi xizmat testda chaqirilsa istisno tashlasin - bu ataylab qo'yilgan tuzoq.
- SQLite sanani matn sifatida saqlaydi; tur o'girishni faqat integratsiya testi tekshiradi.
- Bo'sh baza va bo'sh ro'yxat testlari izolyatsiya ishlayotganini tasdiqlaydi.
- API testida holat kodi ham, tananing butun tarkibi ham tekshiriladi.
- Ilova bog'liqlikni (ulanishni) tashqaridan olsa, uni testda almashtirish oson bo'ladi.
.coveragercsozlamalarni bir joyda saqlaydi va test fayllarini hisobdan chiqaradi.pragma: no coverfaqat haqiqatan testlanmasligi kerak bo'lgan kodga qo'yiladi.--strict-markerse'lon qilinmagan belgidagi imlo xatosidan himoya qiladi.- Tez to'plam har kommitda ishlaydi - va faqat shunda u haqiqatan foyda beradi.
- Mavjud loyihada eng qimmat mantiqdan boshlang va yangi kodni testsiz kiritmang.
- Bir necha oy davomida shu qoidaga amal qilsangiz, to'plam o'z-o'zidan paydo bo'ladi.
Darslik shu yerda tugadi. Endi eng muhim qadam qoldi: o'z loyihangizni ochib, birinchi testni yozing.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.