Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. TestClient nima
- 2.2. Status va javob tekshiruvi
- 2.3. Xato holatlarini test
- 2.4. dependency_overrides bilan almashtirish
- 2.5. Test bazasi (izolyatsiya)
- 2.6. pytest bilan test to'plami
- 2.7. Fixture bilan tayyorlash
- 2.8. Testlash strategiyasi
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — TestClient bilan endpoint test
- Misol 2 — dependency_overrides bilan auth almashtirish
- Misol 3 — pytest bilan test to'plami
- Misol 4 — Amaliy: to'liq test to'plami (test bazasi bilan)
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
20.15-dars: Testlash
20-QISM — WEB-BACKEND (FastAPI) · 15-dars
1. Kirish va motivatsiya
API yozdik — lekin u to'g'ri ishlaydimi? Har endpoint kutilgan javob beradimi? Xato holatlar to'g'ri boshqariladimi? Autentifikatsiya himoya qiladimi? Buni har o'zgartirishda qo'lda tekshirish — sekin va ishonchsiz. Yechim — avtomatik testlar (17-qism).
Bu darsda FastAPI ilovasini testlashni o'rganamiz. FastAPI TestClient beradi — u serverni ishga tushirmasdan endpointlarni chaqiradi (tez, izolyatsiyalangan). pytest (17-qism) bilan birga — har endpoint, xato holat, auth va baza test qilinadi. Test — API sifatining kafolati: o'zgartirishda nima buzilganini darrov ko'rsatadi.
Real vaziyat. Bir jamoa API'ni qo'lda test qilardi (Postman bilan bosib ko'rib). Har release'dan oldin soatlab qo'lda tekshiruv, va baribir xatolar o'tib ketardi — bir marta login endpointi buzildi, hech kim sezmadi, foydalanuvchilar kira olmay qoldi. Jamoa TestClient + pytest testlar yozdi: har endpoint, auth, xato holat. Endi har o'zgarish test to'plamini ishga tushiradi — buzilgan narsa darrov ko'rinadi. Qo'lda test yo'qoldi, ishonch oshdi.
Bu darsda FastAPI testlashni — TestClient, dependency_overrides va pytest bilan o'rganamiz.
Bu darsda:
-
TestClientbilan endpoint test - Status va javob tekshiruvi (
assert) - Xato holatlarini test
dependency_overridesbilan bog'liqlik almashtirish- Test bazasi (izolyatsiya)
pytestbilan test to'plami- Autentifikatsiyani test
- Amaliy: to'liq test to'plami
ℹ Misollarda FastAPI
TestClientvapytestbilan sinaladi.
2. Nazariya — chuqur tushuntirish
2.1. TestClient nima
TestClient — serverni ishga tushirmasdan endpointlarni chaqiradi:
from fastapi.testclient import TestClient
client = TestClient(app)
javob = client.get("/kurslar/1")
javob.status_code # 200
javob.json() # {"id": 1, ...}| Metod | HTTP |
|---|---|
client.get(url) |
GET |
client.post(url, json=...) |
POST |
client.put, client.patch, client.delete |
mos metod |
TestClient — haqiqiy server (port, tarmoq) kerak emas. U ilovani to'g'ridan chaqiradi (tez, izolyatsiyalangan). Javob requests/httpx kabi (status_code, json(), headers).
2.2. Status va javob tekshiruvi
Test — kutilgan natijani assert bilan tekshiradi (17-qism):
def test_kurs_topildi():
javob = client.get("/kurslar/1")
assert javob.status_code == 200
assert javob.json()["nom"] == "Python" Har test bir holatni tekshiradi: status kod to'g'rimi, javob mazmuni to'g'rimi. assert — kutish buzilsa test yiqiladi. Test nomi (test_kurs_topildi) nima sinalishini aytadi.
2.3. Xato holatlarini test
Nafaqat muvaffaqiyat — xato holatlar ham test qilinadi:
def test_kurs_topilmadi():
javob = client.get("/kurslar/999")
assert javob.status_code == 404
def test_validatsiya_xatosi():
javob = client.post("/kurslar", json={"nom": "X"}) # narx yo'q
assert javob.status_code == 422Xato holatlar (404, 422, 401, 403) — muvaffaqiyat kabi muhim. Yaxshi test to'plami ham to'g'ri, ham xato yo'llarni qamraydi. Chegara holatlarni unutmang.
2.4. dependency_overrides bilan almashtirish
Testda bog'liqlik 20.7-bob ni soxta bilan almashtirish:
def soxta_user():
return {"ism": "test_user", "rol": "admin"}
app.dependency_overrides[joriy_foydalanuvchi] = soxta_user
def test_himoyalangan():
javob = client.get("/profil") # auth soxta bilan
assert javob.status_code == 200
app.dependency_overrides.clear() # testdan keyin tozalash dependency_overrides — bog'liqlikni (auth, baza) testda almashtiradi. Bu haqiqiy auth/baza kerak bo'lmasdan test qilishga imkon beradi (DI kuchi, 20.7). Testdan keyin clear() bilan tozalang.
2.5. Test bazasi (izolyatsiya)
Testda haqiqiy baza emas, alohida test bazasi:
def test_baza():
return sqlite3.connect(":memory:") # yoki temp fayl
app.dependency_overrides[baza] = test_baza Test bazasi — haqiqiy ma'lumotga tegmaydi (izolyatsiya). :memory: yoki vaqtincha fayl — har test toza baza bilan. Bu testlarni mustaqil qiladi (biri boshqasiga ta'sir qilmaydi).
2.6. pytest bilan test to'plami
pytest (17-qism) testlarni topib ishga tushiradi:
# test_api.py
def test_kurs_topildi(): ...
def test_kurs_topilmadi(): ...$ pytest -v
test_api.py::test_kurs_topildi PASSED
test_api.py::test_kurs_topilmadi PASSED pytest — test_ bilan boshlangan funksiyalarni topadi va ishga tushiradi. -v — har testni nomi bilan ko'rsatadi. Yashil (PASSED) — o'tdi, qizil (FAILED) — buzilgan.
2.7. Fixture bilan tayyorlash
pytest fixture 17.3-bob — test uchun tayyorgarlik:
import pytest
@pytest.fixture
def client():
return TestClient(app)
def test_kurs(client): # fixture avtomatik beriladi
assert client.get("/kurslar/1").status_code == 200 Fixture — takrorlanuvchi tayyorgarlikni (client, test bazasi, test foydalanuvchi) bir joyga yig'adi. Test uni parametr sifatida oladi — pytest avtomatik beradi.
2.8. Testlash strategiyasi
| Test turi | Nima tekshiradi |
|---|---|
| Muvaffaqiyat | To'g'ri javob (200, mazmun) |
| Xato | 404, 422, 401, 403 |
| Chegara | Bo'sh, katta, chegara qiymat |
| Auth | Kirgan/kirmagan, rol |
| CRUD | Har amal (yaratish→o'qish→o'chirish) |
To'liq test to'plami har yo'lni qamraydi: muvaffaqiyat, xato, chegara, auth. Test hujjat ham — API qanday ishlashini ko'rsatadi. Yozilgan har endpoint testga ega bo'lsin.
3. Tez ma'lumotnoma
from fastapi.testclient import TestClient
import pytest
client = TestClient(app)
# oddiy test:
def test_kurs():
javob = client.get("/kurslar/1")
assert javob.status_code == 200
assert javob.json()["nom"] == "Python"
# bog'liqlik almashtirish:
app.dependency_overrides[joriy_foydalanuvchi] = lambda: {"ism": "test", "rol": "admin"}
# ... test ...
app.dependency_overrides.clear()
# fixture:
@pytest.fixture
def client():
return TestClient(app)Testlash
TestClient — serversiz endpoint chaqirish
assert — status va javob tekshiruvi
dependency_overrides — auth/baza almashtirish
pytest -v — test to'plamini ishga tushirish4. Batafsil misollar
Misollarda FastAPI
TestClientvapytestbilan sinaladi.
Misol 1 — TestClient bilan endpoint test
"""TestClient bilan endpoint chaqirish; status va javob tekshiruvi (assert); muvaffaqiyat va xato holatlar."""
import warnings
warnings.filterwarnings("ignore")
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
BAZA = {1: "Python", 2: "Go"}
class Kurs(BaseModel):
nom: str
@app.get("/kurslar/{kurs_id}")
def kurs(kurs_id: int):
if kurs_id not in BAZA:
raise HTTPException(status_code=404, detail="topilmadi")
return {"id": kurs_id, "nom": BAZA[kurs_id]}
@app.post("/kurslar", status_code=201)
def yarat(kurs: Kurs):
yangi_id = max(BAZA) + 1
BAZA[yangi_id] = kurs.nom
return {"id": yangi_id, "nom": kurs.nom}
client = TestClient(app)
def main() -> None:
natijalar = []
# 1. Muvaffaqiyatli GET
javob = client.get("/kurslar/1")
natijalar.append(("kurs topildi (200)", javob.status_code == 200 and javob.json()["nom"] == "Python"))
# 2. 404 xato
javob = client.get("/kurslar/999")
natijalar.append(("kurs topilmadi (404)", javob.status_code == 404))
# 3. POST 201
javob = client.post("/kurslar", json={"nom": "Rust"})
natijalar.append(("kurs yaratildi (201)", javob.status_code == 201))
# 4. Validatsiya 422
javob = client.post("/kurslar", json={})
natijalar.append(("validatsiya (422)", javob.status_code == 422))
print("=== Test natijalari ===")
for nom, otdi in natijalar:
print(f" {'✅ PASSED' if otdi else '❌ FAILED'}: {nom}")
print(f"\n jami: {sum(o for _, o in natijalar)}/{len(natijalar)} o'tdi")
print(" ⭐ TestClient — serversiz endpoint chaqirish; assert — tekshiruv")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== Test natijalari ===
✅ PASSED: kurs topildi (200)
✅ PASSED: kurs topilmadi (404)
✅ PASSED: kurs yaratildi (201)
✅ PASSED: validatsiya (422)
jami: 4/4 o'tdi
⭐ TestClient — serversiz endpoint chaqirish; assert — tekshiruvNima ko'rsatdi: 2.1, 2.2, 2.3-bo'limlar.
Misol 2 — dependency_overrides bilan auth almashtirish
"""dependency_overrides bilan autentifikatsiyani soxta bilan almashtirish; haqiqiy auth'siz test; tozalash."""
import warnings
warnings.filterwarnings("ignore")
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException
from fastapi.testclient import TestClient
app = FastAPI()
def joriy_foydalanuvchi(x_token: Annotated[str | None, Header()] = None):
if x_token != "haqiqiy-token":
raise HTTPException(status_code=401, detail="kirmagan")
return {"ism": "ali", "rol": "oquvchi"}
@app.get("/profil")
def profil(user: Annotated[dict, Depends(joriy_foydalanuvchi)]):
return {"ism": user["ism"], "rol": user["rol"]}
client = TestClient(app)
def main() -> None:
natijalar = []
# 1. Override'siz — auth kerak
javob = client.get("/profil")
natijalar.append(("override'siz 401", javob.status_code == 401))
# 2. Override bilan — soxta admin
app.dependency_overrides[joriy_foydalanuvchi] = lambda: {"ism": "test", "rol": "admin"}
javob = client.get("/profil")
natijalar.append(("override bilan 200", javob.status_code == 200))
natijalar.append(("soxta user qaytdi", javob.json()["ism"] == "test"))
# 3. Tozalash
app.dependency_overrides.clear()
javob = client.get("/profil")
natijalar.append(("tozalangach yana 401", javob.status_code == 401))
print("=== Test natijalari ===")
for nom, otdi in natijalar:
print(f" {'✅' if otdi else '❌'} {nom}")
print(f"\n jami: {sum(o for _, o in natijalar)}/{len(natijalar)} o'tdi")
print(" ⭐ dependency_overrides — auth/bazani soxta bilan almashtirish")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== Test natijalari ===
✅ override'siz 401
✅ override bilan 200
✅ soxta user qaytdi
✅ tozalangach yana 401
jami: 4/4 o'tdi
⭐ dependency_overrides — auth/bazani soxta bilan almashtirishNima ko'rsatdi: 2.4, 2.5-bo'limlar.
Misol 3 — pytest bilan test to'plami
"""pytest bilan FastAPI test; test funksiyalari; -v natija (PASSED); subprocess bilan deterministik ishga tushirish."""
import warnings
warnings.filterwarnings("ignore")
import os
import re
import subprocess
import sys
import tempfile
import textwrap
TEST_KODI = textwrap.dedent('''
import warnings
warnings.filterwarnings("ignore")
from fastapi import FastAPI, HTTPException
from fastapi.testclient import TestClient
app = FastAPI()
BAZA = {1: "Python"}
@app.get("/kurslar/{kurs_id}")
def kurs(kurs_id: int):
if kurs_id not in BAZA:
raise HTTPException(404, "topilmadi")
return {"id": kurs_id, "nom": BAZA[kurs_id]}
client = TestClient(app)
def test_kurs_topildi():
javob = client.get("/kurslar/1")
assert javob.status_code == 200
assert javob.json()["nom"] == "Python"
def test_kurs_topilmadi():
assert client.get("/kurslar/999").status_code == 404
def test_notogri_id():
assert client.get("/kurslar/abc").status_code == 422
''')
def main() -> None:
with tempfile.TemporaryDirectory() as papka:
fayl = os.path.join(papka, "test_api.py")
with open(fayl, "w", encoding="utf-8") as f:
f.write(TEST_KODI)
natija = subprocess.run(
[sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", "-v", "--tb=no", fayl],
capture_output=True, text=True, cwd=papka,
)
print("=== pytest natijasi (deterministik) ===")
for qator in natija.stdout.splitlines():
if re.search(r"\bPASSED\b|\bFAILED\b", qator):
q = re.sub(r"^.*test_api\.py", "test_api.py", qator)
q = re.sub(r"\s*\[\s*\d+%\]", "", q)
print(f" {q.rstrip()}")
moslik = re.search(r"(\d+) passed", natija.stdout)
print(f"\n xulosa: {moslik.group(0) if moslik else '?'}")
print(" ⭐ pytest -v — test_ funksiyalarini topib ishga tushiradi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== pytest natijasi (deterministik) ===
test_api.py::test_kurs_topildi PASSED
test_api.py::test_kurs_topilmadi PASSED
test_api.py::test_notogri_id PASSED
xulosa: 3 passed
⭐ pytest -v — test_ funksiyalarini topib ishga tushiradiNima ko'rsatdi: 2.6-bo'lim.
Misol 4 — Amaliy: to'liq test to'plami (test bazasi bilan)
To'liq CRUD API'ni test bazasi va auth override bilan test qilamiz: har amal (yaratish, o'qish, o'chirish), xato holatlar va autentifikatsiya. Bu — real API test to'plamining namunasi.
"""to'liq test: test bazasi (dependency_overrides), CRUD (yaratish→o'qish→o'chirish), xato holatlar, auth override."""
import warnings
warnings.filterwarnings("ignore")
import os
import sqlite3
import tempfile
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException, status
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
def baza():
# ishlab chiqarish bazasi (test'da almashtiriladi)
raise RuntimeError("haqiqiy baza test'da ishlatilmasin")
def joriy_admin(x_token: Annotated[str | None, Header()] = None):
if x_token != "admin":
raise HTTPException(status_code=401, detail="admin kerak")
return {"rol": "admin"}
class Kurs(BaseModel):
nom: str
@app.post("/kurslar", status_code=201)
def yarat(kurs: Kurs, db: Annotated[sqlite3.Connection, Depends(baza)], user: Annotated[dict, Depends(joriy_admin)]):
cur = db.execute("INSERT INTO kurslar (nom) VALUES (?)", (kurs.nom,))
db.commit()
return {"id": cur.lastrowid, "nom": kurs.nom}
@app.get("/kurslar/{kurs_id}")
def kurs(kurs_id: int, db: Annotated[sqlite3.Connection, Depends(baza)]):
q = db.execute("SELECT id, nom FROM kurslar WHERE id = ?", (kurs_id,)).fetchone()
if q is None:
raise HTTPException(status_code=404, detail="topilmadi")
return {"id": q[0], "nom": q[1]}
@app.delete("/kurslar/{kurs_id}", status_code=status.HTTP_204_NO_CONTENT)
def ochir(kurs_id: int, db: Annotated[sqlite3.Connection, Depends(baza)], user: Annotated[dict, Depends(joriy_admin)]):
db.execute("DELETE FROM kurslar WHERE id = ?", (kurs_id,))
db.commit()
def main() -> None:
# test bazasi (temp fayl, izolyatsiya)
tmp = tempfile.mkdtemp()
db_yol = os.path.join(tmp, "test.db")
sozla = sqlite3.connect(db_yol)
sozla.execute("CREATE TABLE kurslar (id INTEGER PRIMARY KEY AUTOINCREMENT, nom TEXT)")
sozla.commit()
sozla.close()
def test_baza():
conn = sqlite3.connect(db_yol)
try:
yield conn
finally:
conn.close()
# bog'liqlikni test bazasi bilan almashtirish
app.dependency_overrides[baza] = test_baza
client = TestClient(app)
natijalar = []
# 1. Yaratish (auth bilan)
javob = client.post("/kurslar", json={"nom": "Python"}, headers={"X-Token": "admin"})
natijalar.append(("yaratish 201", javob.status_code == 201))
kurs_id = javob.json()["id"]
# 2. O'qish
javob = client.get(f"/kurslar/{kurs_id}")
natijalar.append(("o'qish 200", javob.status_code == 200 and javob.json()["nom"] == "Python"))
# 3. Auth'siz yaratish — 401
javob = client.post("/kurslar", json={"nom": "X"})
natijalar.append(("auth'siz 401", javob.status_code == 401))
# 4. O'chirish (auth bilan) → 204, keyin 404
javob = client.delete(f"/kurslar/{kurs_id}", headers={"X-Token": "admin"})
natijalar.append(("o'chirish 204", javob.status_code == 204))
natijalar.append(("o'chirilgach 404", client.get(f"/kurslar/{kurs_id}").status_code == 404))
app.dependency_overrides.clear()
print("=== To'liq test to'plami ===")
for nom, otdi in natijalar:
print(f" {'✅' if otdi else '❌'} {nom}")
print(f"\n jami: {sum(o for _, o in natijalar)}/{len(natijalar)} o'tdi")
print(" ⭐ test bazasi + auth override — izolyatsiyalangan to'liq test")
import shutil
shutil.rmtree(tmp, ignore_errors=True)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== To'liq test to'plami ===
✅ yaratish 201
✅ o'qish 200
✅ auth'siz 401
✅ o'chirish 204
✅ o'chirilgach 404
jami: 5/5 o'tdi
⭐ test bazasi + auth override — izolyatsiyalangan to'liq testNima ko'rsatdi: 2.4, 2.5, 2.8-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Test uchun server kerak" | TestClient — serversiz |
| "Faqat muvaffaqiyat test" | Xato holatlar ham |
| "Haqiqiy auth bilan test" | dependency_overrides |
| "Haqiqiy baza test'da" | Test bazasi (izolyatsiya) |
| "Qo'lda test yetarli" | Avtomatik (pytest) |
| "Testlar bog'liq" | Mustaqil bo'lsin |
| "Test — vaqt isrofi" | Test — sifat kafolati |
| "Override tozalamaslik" | clear() — testdan keyin |
6. Keng tarqalgan xatolar va yechimlari
1. Faqat muvaffaqiyatni test qilish
def test_kurs(): assert client.get("/kurslar/1").status_code == 200
# ✅ xato holatni ham: 404, 422, 4012. Override'ni tozalamaslik
app.dependency_overrides[auth] = soxta # ⚠️ keyingi testga ta'sir
# ✅ app.dependency_overrides.clear() (yoki fixture)3. Haqiqiy baza test'da
app.dependency_overrides[baza] = haqiqiy # ⚠️ ma'lumot buziladi
app.dependency_overrides[baza] = test_baza # ✅ izolyatsiya4. Testlar bir-biriga bog'liq
def test_1(): global_holat.append(...) # ⚠️ test_2 ga ta'sir
# ✅ har test toza holat (fixture)5. assertsiz test
def test_kurs(): client.get("/kurslar/1") # ❌ hech nima tekshirmaydi
def test_kurs(): assert client.get(...).status_code == 200 # ✅6. Test nomi tushunarsiz
def test_1(): ... # ❌ nima sinaladi?
def test_kurs_topilmadi_404(): ... # ✅ aniq7. JSON javobni tekshirmaslik
assert javob.status_code == 200 # ⚠️ mazmun-chi?
assert javob.json()["nom"] == "Python" # ✅ mazmun ham7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 17-qism (o'tilgan): pytest, fixture, assert, parametrize
- 20.7-dars (o'tilgan): bog'liqlik —
dependency_overrides - 20.8-dars (o'tilgan): baza — test bazasi
- 20.10-dars (o'tilgan): auth — override bilan test
- 18-qism (o'tilgan): coverage — test qamrovini o'lchash
8. Eng yaxshi amaliyotlar
TestClientbilan test qiling (server kerak emas).Ham muvaffaqiyat, ham xato holatlarini test qiling.
dependency_overridesbilan auth/bazani almashtiring.Test bazasi (izolyatsiya) — haqiqiy ma'lumotga tegmang.
Testlar mustaqil bo'lsin (biri boshqasiga ta'sir qilmasin).
Override'ni testdan keyin
clear()qiling.Aniq test nomlari (
test_kurs_topilmadi).Status va javob mazmunini ikkalasini tekshiring.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # TestClient nima uchun?
2. # server kerakmi?
3. # assert nima qiladi?
4. # xato holatni test qilamizmi?
5. # dependency_overrides nima?
6. # test bazasi nega?
7. # override'ni nima qilamiz (test'dan keyin)?
8. # pytest test'ni qanday topadi?
9. # -v nima ko'rsatadi?
10. # fixture nima?
11. # testlar bog'liqmi?
12. # nima tekshiramiz (status/mazmun)?Javoblar
- Serverni ishga tushirmasdan endpoint chaqirish
- Yo'q (
TestClient— serversiz) - Kutish buzilsa test yiqiladi
- Ha (404, 422, 401, 403)
- Bog'liqlikni (auth/baza) soxta bilan almashtirish
- Izolyatsiya (haqiqiy ma'lumotga tegmaslik)
clear()(tozalash)test_bilan boshlangan funksiyalar- Har testni nomi bilan (PASSED/FAILED)
- Test uchun takrorlanuvchi tayyorgarlik
- Yo'q (mustaqil bo'lsin)
- Ikkalasi (status kod va javob mazmuni)
Vazifa 2: Xatolarni tuzating
1. def test_kurs(): assert client.get("/kurslar/1").status_code == 200
# faqat muvaffaqiyat
2. app.dependency_overrides[auth] = soxta # tozalash yo'q
3. app.dependency_overrides[baza] = haqiqiy # test baza
4. def test_kurs(): client.get("/kurslar/1") # assert
5. def test_1(): ... # nomJavoblar
1. # + def test_kurs_404(): assert client.get("/kurslar/999").status_code == 404
2. app.dependency_overrides.clear() # testdan keyin
3. app.dependency_overrides[baza] = test_baza
4. def test_kurs(): assert client.get("/kurslar/1").status_code == 200
5. def test_kurs_topildi(): ...Vazifa 3: To'liq endpoint testlari
Kurs API uchun test to'plami:
- GET (200 va 404), POST (201 va 422)
- Har biri alohida test funksiyasi
- Status va mazmun tekshiruvi
pytest -vbilan ishga tushirish
Vazifa 4: Auth testlari
Autentifikatsiyani test qiling:
- Kirmagan (401), noto'g'ri token (401)
dependency_overridesbilan soxta user- Rol testlari (admin/oddiy)
- Override tozalash
Vazifa 5: Test bazasi bilan CRUD
Izolyatsiyalangan CRUD test:
- Test bazasi (
:memory:yoki temp) - Yaratish→o'qish→yangilash→o'chirish ketma-ketligi
- Har test toza baza (fixture)
- Xato holatlar
Vazifa 6: Fixture bilan tashkil
Testlarni fixture bilan tartibga soling:
clientfixturetest_bazafixturetest_userfixture (soxta auth)- Testlar fixture'lardan foydalanadi
Vazifa 7: O'ylash
TestClient va dependency_overrides FastAPI'ni oson sinaladigan qiladi: server, haqiqiy baza yoki haqiqiy auth kerak emas — hammasi soxta bilan almashtiriladi. Bu Dependency Injection 20.7-bob ning bevosita natijasi: bog'liqlik e'lon qilingan (yaratilgan emas), shuning uchun almashtiriladi. "Sinaluvchanlik" (testability) nima uchun yaxshi dizayn belgisi, va DI kabi tamoyillar kod'ni qanday sinaluvchan qiladi?
Javob
Qisqa javob: Sinaluvchanlik (testability) — kod'ni test qilishning osonligi. FastAPI oson sinaladi, chunki bog'liqliklar (baza, auth) e'lon qilingan (DI, 20.7), yaratilgan emas — shuning uchun testda soxta bilan almashtiriladi (dependency_overrides). Sinaluvchanlik yaxshi dizayn belgisi, chunki oson sinaladigan kod odatda yaxshi ajratilgan (bog'liqliklar aniq, qismlar mustaqil). DI kabi tamoyillar kod'ni sinaluvchan qiladi: bog'liqlik tashqaridan beriladi, testda uni almashtirish mumkin.
1. Sinaluvchanlik nima
Kod'ni izolyatsiyalab, tashqi narsasiz (server, baza, tarmoq) test qilishning osonligi.
2. DI sinaluvchanlikni beradi
| DIsiz (qattiq bog'liq) | DI bilan (sinaluvchan) |
|---|---|
baza = Baza() ichida |
Depends(baza) |
| Haqiqiy baza kerak | dependency_overrides |
| Test qiyin | Test oson |
3. Sinaluvchanlik = yaxshi dizayn
Oson sinaladigan kod odatda:
- Yaxshi ajratilgan (bog'liqlik aniq)
- Mustaqil qismlar
- Aniq interfeys
Sinash qiyin bo'lsa — dizayn muammosi belgisi (juda bog'liq, aralashgan).
4. Qaysi tamoyillar sinaluvchanlikni beradi
| Tamoyil | Qanday yordam |
|---|---|
| DI | Bog'liqlik almashtiriladi |
| Ajratish | Qism mustaqil sinaladi |
| Interfeys | Soxta (mock) beriladi |
| Toza funksiya | Kirish→chiqish oson |
5. Muhandislik saboqlari
- Sinaluvchanlik — yaxshi dizayn belgisi
- DI — bog'liqlikni almashtiriladigan qiladi
- Sinash qiyin → dizayn muammosi
- Test'ni o'ylab dizayn qil (test-driven)
6. Xulosa
- Sinaluvchanlik — test qilishning osonligi
- DI — bog'liqlik almashtiriladi (
overrides) - Oson sinaladi = yaxshi ajratilgan
- Sinaluvchanlik — dizayn sifat belgisi
Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.
Xulosa
Bu darsda FastAPI ilovasini testlashni o'rgandik.
Eng muhim uch fikr:
TestClient— serversiz test.TestClient(app)ilovani haqiqiy server (port, tarmoq) kerak bo'lmasdan chaqiradi — tez, izolyatsiyalangan. Endpointlarclient.get,client.postbilan chaqiriladi, javobstatus_codevajson()bilan tekshiriladi. Test kutilgan natijaniassertbilan tasdiqlaydi: status kod va javob mazmuni. Yaxshi test ham muvaffaqiyat (200), ham xato holatlarni (404, 422, 401, 403) qamraydi.dependency_overridesva izolyatsiya. Testda bog'liqlik (auth, baza) soxta bilan almashtiriladi:app.dependency_overrides[joriy_foydalanuvchi] = soxta_user. Bu haqiqiy auth/baza kerak bo'lmasdan test qilishga imkon beradi — Dependency Injection 20.7-bob ning bevosita natijasi. Test bazasi (:memory:yoki temp fayl) haqiqiy ma'lumotga tegmaydi (izolyatsiya). Override'ni testdan keyinclear()qiling, testlar mustaqil bo'lsin.pytestva sinaluvchanlik.pytest -vtest_bilan boshlangan funksiyalarni topadi va ishga tushiradi (PASSED/FAILED). Fixture 17.3-bob takrorlanuvchi tayyorgarlikni (client, test bazasi, soxta user) bir joyga yig'adi. FastAPI oson sinaladi, chunki bog'liqliklar e'lon qilingan (yaratilgan emas) — bu "sinaluvchanlik" (testability), yaxshi dizayn belgisi: oson sinaladigan kod yaxshi ajratilgan. Yozilgan har endpoint testga ega bo'lsin — test API sifatining kafolati va hujjati.
Keyingi darsda hujjatlar va OpenAPI ni o'rganamiz: FastAPI avtomatik yaratadigan interaktiv hujjatlar (/docs, /redoc), OpenAPI sxemasini sozlash, endpoint tavsiflari va bu 20-qismning yakuni.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!