IlmHamroh
Python kursi/Web backend FastAPI15/16-dars16 daqiqa
Mundarija (22)

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:

  • TestClient bilan endpoint test
  • Status va javob tekshiruvi (assert)
  • Xato holatlarini test
  • dependency_overrides bilan bog'liqlik almashtirish
  • Test bazasi (izolyatsiya)
  • pytest bilan test to'plami
  • Autentifikatsiyani test
  • Amaliy: to'liq test to'plami

ℹ Misollarda FastAPI TestClient va pytest bilan sinaladi.


2. Nazariya — chuqur tushuntirish

2.1. TestClient nima

TestClient — serverni ishga tushirmasdan endpointlarni chaqiradi:

python
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):

python
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:

python
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 == 422

Xato 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:

python
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:

python
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:

python
# 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:

python
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

python
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 tushirish

4. Batafsil misollar

Misollarda FastAPI TestClient va pytest bilan sinaladi.

Misol 1 — TestClient bilan endpoint test

python
"""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:

text
=== 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 — tekshiruv

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

Misol 2 — dependency_overrides bilan auth almashtirish

python
"""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:

text
=== 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 almashtirish

Nima ko'rsatdi: 2.4, 2.5-bo'limlar.

Misol 3 — pytest bilan test to'plami

python
"""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:

text
=== 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 tushiradi

Nima 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.

python
"""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:

text
=== 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 test

Nima 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

python
def test_kurs(): assert client.get("/kurslar/1").status_code == 200
# ✅ xato holatni ham: 404, 422, 401

2. Override'ni tozalamaslik

python
app.dependency_overrides[auth] = soxta      # ⚠️ keyingi testga ta'sir
# ✅ app.dependency_overrides.clear() (yoki fixture)

3. Haqiqiy baza test'da

python
app.dependency_overrides[baza] = haqiqiy    # ⚠️ ma'lumot buziladi
app.dependency_overrides[baza] = test_baza  # ✅ izolyatsiya

4. Testlar bir-biriga bog'liq

python
def test_1(): global_holat.append(...)      # ⚠️ test_2 ga ta'sir
# ✅ har test toza holat (fixture)

5. assertsiz test

python
def test_kurs(): client.get("/kurslar/1")   # ❌ hech nima tekshirmaydi
def test_kurs(): assert client.get(...).status_code == 200   # ✅

6. Test nomi tushunarsiz

python
def test_1(): ...                            # ❌ nima sinaladi?
def test_kurs_topilmadi_404(): ...           # ✅ aniq

7. JSON javobni tekshirmaslik

python
assert javob.status_code == 200              # ⚠️ mazmun-chi?
assert javob.json()["nom"] == "Python"       # ✅ mazmun ham

7. 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

  1. TestClient bilan test qiling (server kerak emas).

  2. Ham muvaffaqiyat, ham xato holatlarini test qiling.

  3. dependency_overrides bilan auth/bazani almashtiring.

  4. Test bazasi (izolyatsiya) — haqiqiy ma'lumotga tegmang.

  5. Testlar mustaqil bo'lsin (biri boshqasiga ta'sir qilmasin).

  6. Override'ni testdan keyin clear() qiling.

  7. Aniq test nomlari (test_kurs_topilmadi).

  8. Status va javob mazmunini ikkalasini tekshiring.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
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
  1. Serverni ishga tushirmasdan endpoint chaqirish
  2. Yo'q (TestClient — serversiz)
  3. Kutish buzilsa test yiqiladi
  4. Ha (404, 422, 401, 403)
  5. Bog'liqlikni (auth/baza) soxta bilan almashtirish
  6. Izolyatsiya (haqiqiy ma'lumotga tegmaslik)
  7. clear() (tozalash)
  8. test_ bilan boshlangan funksiyalar
  9. Har testni nomi bilan (PASSED/FAILED)
  10. Test uchun takrorlanuvchi tayyorgarlik
  11. Yo'q (mustaqil bo'lsin)
  12. Ikkalasi (status kod va javob mazmuni)

Vazifa 2: Xatolarni tuzating

python
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(): ...                          # nom
Javoblar
python
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:

  1. GET (200 va 404), POST (201 va 422)
  2. Har biri alohida test funksiyasi
  3. Status va mazmun tekshiruvi
  4. pytest -v bilan ishga tushirish

Vazifa 4: Auth testlari

Autentifikatsiyani test qiling:

  1. Kirmagan (401), noto'g'ri token (401)
  2. dependency_overrides bilan soxta user
  3. Rol testlari (admin/oddiy)
  4. Override tozalash

Vazifa 5: Test bazasi bilan CRUD

Izolyatsiyalangan CRUD test:

  1. Test bazasi (:memory: yoki temp)
  2. Yaratish→o'qish→yangilash→o'chirish ketma-ketligi
  3. Har test toza baza (fixture)
  4. Xato holatlar

Vazifa 6: Fixture bilan tashkil

Testlarni fixture bilan tartibga soling:

  1. client fixture
  2. test_baza fixture
  3. test_user fixture (soxta auth)
  4. 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

  1. Sinaluvchanlik — yaxshi dizayn belgisi
  2. DI — bog'liqlikni almashtiriladigan qiladi
  3. Sinash qiyin → dizayn muammosi
  4. Test'ni o'ylab dizayn qil (test-driven)

6. Xulosa

  1. Sinaluvchanlik — test qilishning osonligi
  2. DI — bog'liqlik almashtiriladi (overrides)
  3. Oson sinaladi = yaxshi ajratilgan
  4. Sinaluvchanlik — dizayn sifat belgisi

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda FastAPI ilovasini testlashni o'rgandik.

Eng muhim uch fikr:

  1. TestClient — serversiz test. TestClient(app) ilovani haqiqiy server (port, tarmoq) kerak bo'lmasdan chaqiradi — tez, izolyatsiyalangan. Endpointlar client.get, client.post bilan chaqiriladi, javob status_code va json() bilan tekshiriladi. Test kutilgan natijani assert bilan tasdiqlaydi: status kod va javob mazmuni. Yaxshi test ham muvaffaqiyat (200), ham xato holatlarni (404, 422, 401, 403) qamraydi.

  2. dependency_overrides va 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 keyin clear() qiling, testlar mustaqil bo'lsin.

  3. pytest va sinaluvchanlik. pytest -v test_ 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.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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