IlmHamroh
Python kursi/Testlash3/10-dars19 daqiqa
Mundarija (22)

17.3-dars: Fixture

17-QISM — TESTLASH · 3-dars


1. Kirish va motivatsiya

O'tgan darslardagi testlarda bir muammo bor edi: har testda bir xil tayyorlash kodini takrorladik. Har savat testida s = Savat(), har parol testida bir xil test ma'lumoti. Bu takror — kodning dushmani. Yechim — fixture: testga kerakli narsani (obyekt, ma'lumot, ulanish, vaqtinchalik fayl) tayyorlab beruvchi funksiya.

Fixture — pytest ning eng kuchli va o'ziga xos xususiyati. U testni toza (faqat tekshiruv mantiqi), mustaqil (har test o'z yangi ma'lumotini oladi) va oson o'qiladigan qiladi.

Real vaziyat. Bir jamoa baza bilan ishlaydigan kodni test qilardi. Har testda: bazaga ulanish, jadval yaratish, sinov ma'lumotini yozish, test bajarish, keyin tozalash. Bu 10 qator kod har testda takrorlanardi — 50 test = 500 qator takror. Bittasida tozalash unutildi va testlar bir-biriga ta'sir qila boshladi: bitta test yozgan ma'lumot boshqasini buzardi. Ular fixture ga o'tishdi: ulanish va tozalash bir marta fixture da yozildi, har test uni parametr sifatida so'rab, avtomatik yangi, toza bazaga ega bo'ldi.

Bu darsda fixture larni — testga kerak narsani tayyorlab, keyin tozalab beruvchi mexanizmni o'rganamiz.

Bu darsda:

  • @pytest.fixture — nima va nega
  • Fixture ni parametr sifatida so'rash
  • yield fixture — tayyorlash va tozalash (setup/teardown)
  • Ko'lam (scope): function, class, module, session
  • Fixture fixture ni ishlatishi (kompozitsiya)
  • O'rnatilgan fixture lar: tmp_path, monkeypatch, capsys
  • conftest.py — fixture larni ulashish
  • Amaliy: baza fixture bilan repozitoriyni test qilish

2. Nazariya — chuqur tushuntirish

2.1. Fixture nima

Fixture — @pytest.fixture bilan belgilangan funksiya. U testga kerakli narsani tayyorlab qaytaradi. Test uni parametr nomi orqali so'raydi:

python
import pytest

@pytest.fixture
def sonlar():
    return [1, 2, 3, 4, 5]

def test_yigindi(sonlar):          # pytest 'sonlar' fixture ni topib chaqiradi
    assert sum(sonlar) == 15

def test_uzunlik(sonlar):          # har test o'z YANGI ro'yxatini oladi
    assert len(sonlar) == 5

pytest fixture nomini test parametrida ko'rib, uni avtomatik chaqiradi va natijani beradi. Bu — dependency injection (13.5, 20.7).

2.2. Fixture nima uchun

Foyda Tafsilot
Takrorni yo'q qiladi Tayyorlash bir marta yoziladi
Mustaqillik Har test yangi, toza nusxa oladi
Toza test Test faqat tekshiruv mantiqiga e'tibor beradi
Tozalash yield bilan avtomatik teardown
Kompozitsiya Fixture boshqa fixture ni ishlatadi

2.3. yield fixture — setup va teardown

Resurs (fayl, ulanish, server) ishlatilgach tozalanishi kerak bo'lsa, return o'rniga yield:

python
@pytest.fixture
def baza():
    con = ulanish_och()            # SETUP — testdan oldin
    yield con                      # test shu qiymatni oladi
    con.close()                    # TEARDOWN — testdan keyin (test yiqilsa ham)

yield gacha — tayyorlash, yield dan keyin — tozalash. Tozalash test yiqilsa ham bajariladi (xuddi try/finally kabi).

2.4. Ko'lam (scope)

Fixture qancha marta chaqirilishini scope belgilaydi:

Scope Chaqiriladi Qachon
function (sukut) Har test uchun Mustaqillik kerak
class Har sinf uchun bir marta Sinf testlari umumiy
module Har fayl uchun bir marta Qimmat tayyorlash
session Butun ishga tushirishda bir marta Juda qimmat (baza serveri)
python
@pytest.fixture(scope="module")
def qimmat_resurs():
    return qimmat_hisob()          # fayl bo'yicha bir marta

Kengroq scope tezroq, lekin testlar umumiy holatni bo'lishadi — o'zgartiriladigan ma'lumotda ehtiyot bo'ling. Sukut function — eng xavfsiz.

2.5. Fixture kompozitsiyasi

Fixture boshqa fixture ni parametr sifatida so'rashi mumkin:

python
@pytest.fixture
def bosh_savat():
    return Savat()

@pytest.fixture
def toldirilgan_savat(bosh_savat):     # bosh_savat fixture ni ishlatadi
    bosh_savat.qosh("kitob", 45000)
    return bosh_savat

Bu murakkab tayyorlashni kichik, qayta ishlatiladigan qismlarga bo'ladi.

2.6. O'rnatilgan (built-in) fixture lar

pytest tayyor fixture lar beradi:

Fixture Nima beradi
tmp_path Vaqtinchalik Path papka (har test uchun yangi)
monkeypatch Vaqtincha o'zgartirish (env, atribut) — testdan keyin tiklanadi
capsys print/stdout ni ushlash
caplog Jurnal (logging) yozuvlarini ushlash
tmp_path_factory Session bo'yicha vaqtinchalik papka
python
def test_fayl(tmp_path):
    fayl = tmp_path / "test.txt"
    fayl.write_text("salom")
    assert fayl.read_text() == "salom"

def test_muhit(monkeypatch):
    monkeypatch.setenv("REJIM", "test")    # test tugagach avtomatik tiklanadi
    assert os.environ["REJIM"] == "test"

def test_chiqish(capsys):
    print("salom")
    assert capsys.readouterr().out == "salom\n"

monkeypatch — tashqi bog'liqlikni (vaqt, env, funksiya) vaqtincha almashtirishning xavfsiz yo'li; keyingi darsda (mock) chuqurroq.

2.7. conftest.py — fixture larni ulashish

Bir necha test faylida ishlatiladigan fixture ni conftest.py fayliga qo'ying — pytest uni avtomatik topadi, import shart emas:

tests/
  conftest.py          # umumiy fixture lar
  test_savat.py        # conftest fixture larini ishlatadi
  test_tolov.py

conftest.py — o'sha papka va ost-papkalaridagi barcha testga ko'rinadi.

2.8. autouse — avtomatik fixture

autouse=True fixture ni so'ramasdan har testga qo'llaydi (masalan har testdan oldin holatni tiklash):

python
@pytest.fixture(autouse=True)
def har_testdan_oldin():
    tozala_holat()
    yield

Ehtiyotkorlik bilan: yashirin xatti-harakat testni tushunishni qiyinlashtiradi.


3. Tez ma'lumotnoma

python
import pytest

@pytest.fixture
def malumot():
    return {"nom": "test"}

@pytest.fixture
def baza():
    con = och()
    yield con              # setup / teardown
    con.close()

@pytest.fixture(scope="module")
def qimmat(): ...

def test_x(malumot, baza, tmp_path, monkeypatch, capsys):
    ...

Qoidalar

takroriy tayyorlash — fixture ga
resurs (fayl/ulanish) — yield fixture, teardown
sukut scope function — mustaqillik uchun
umumiy fixture — conftest.py
vaqtinchalik fayl — tmp_path
env/atribut vaqtincha — monkeypatch
chiqishni tekshirish — capsys

4. Batafsil misollar

Misollarda pytest ni deterministik ishga tushiruvchi pytest_ishga yordamchisi ishlatiladi.

Misol 1 — Asosiy fixture va kompozitsiya

python
"""fixture ni parametr sifatida so'rash; har test yangi nusxa oladi (mustaqillik); fixture fixture ni ishlatadi (kompozitsiya)."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


KOD = '''
class Savat:
    def __init__(self):
        self.mahsulotlar = []
    def qosh(self, nom):
        self.mahsulotlar.append(nom)
'''

TEST = '''
import pytest
from savat import Savat

@pytest.fixture
def sonlar():
    return [1, 2, 3, 4, 5]

@pytest.fixture
def bosh_savat():
    return Savat()

@pytest.fixture
def toldirilgan_savat(bosh_savat):          # fixture kompozitsiyasi
    bosh_savat.qosh("kitob")
    bosh_savat.qosh("ruchka")
    return bosh_savat

def test_yigindi(sonlar):
    assert sum(sonlar) == 15

def test_royxat_ozgartirsam(sonlar):
    sonlar.append(6)
    assert len(sonlar) == 6

def test_boshqa_test_toza_royxat_oladi(sonlar):
    # oldingi test 6 qo'shdi, lekin bu test YANGI ro'yxat oladi
    assert len(sonlar) == 5

def test_bosh_savat(bosh_savat):
    assert len(bosh_savat.mahsulotlar) == 0

def test_toldirilgan_savat(toldirilgan_savat):
    assert len(toldirilgan_savat.mahsulotlar) == 2
'''


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

        print("=== 1. Fixture bilan testlar ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ test_boshqa_test_toza — mustaqillik: har test yangi ro'yxat oladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Fixture bilan testlar ===
  test_savat.py::test_yigindi PASSED
  test_savat.py::test_royxat_ozgartirsam PASSED
  test_savat.py::test_boshqa_test_toza_royxat_oladi PASSED
  test_savat.py::test_bosh_savat PASSED
  test_savat.py::test_toldirilgan_savat PASSED
  --- 5 passed ---
  chiqish kodi: 0
  ⭐ test_boshqa_test_toza — mustaqillik: har test yangi ro'yxat oladi

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

Misol 2 — yield fixture va scope

python
"""yield fixture: setup va teardown (test yiqilsa ham teardown bajariladi); tozalash tartibi; scope=module bilan bir marta chaqirish."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


TEST = '''
import pytest

HODISALAR = []

@pytest.fixture
def resurs():
    HODISALAR.append("SETUP")
    yield "resurs-obyekti"
    HODISALAR.append("TEARDOWN")

def test_resursdan_foydalanish(resurs):
    HODISALAR.append("TEST ishladi")
    assert resurs == "resurs-obyekti"

def test_tozalash_tartibini_korish():
    # oldingi test tugagach teardown bajarilgan bo'lishi kerak
    assert HODISALAR == ["SETUP", "TEST ishladi", "TEARDOWN"]

# scope=module — butun fayl uchun bir marta
CHAQIRUVLAR = []

@pytest.fixture(scope="module")
def qimmat_resurs():
    CHAQIRUVLAR.append("hisoblandi")
    return {"tayyor": True}

def test_scope_a(qimmat_resurs):
    assert qimmat_resurs["tayyor"]

def test_scope_b(qimmat_resurs):
    assert qimmat_resurs["tayyor"]

def test_scope_bir_marta_chaqirilgan(qimmat_resurs):
    assert len(CHAQIRUVLAR) == 1        # 3 test, lekin 1 marta hisoblandi
'''


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

        print("=== 1. yield fixture: setup/teardown tartibi ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ SETUP → TEST → TEARDOWN tartibi va scope=module bir marta chaqiruv tasdiqlandi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. yield fixture: setup/teardown tartibi ===
  test_yield.py::test_resursdan_foydalanish PASSED
  test_yield.py::test_tozalash_tartibini_korish PASSED
  test_yield.py::test_scope_a PASSED
  test_yield.py::test_scope_b PASSED
  test_yield.py::test_scope_bir_marta_chaqirilgan PASSED
  --- 5 passed ---
  chiqish kodi: 0
  ⭐ SETUP → TEST → TEARDOWN tartibi va scope=module bir marta chaqiruv tasdiqlandi

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

Misol 3 — O'rnatilgan fixture lar: tmp_path, monkeypatch, capsys

python
"""tmp_path — vaqtinchalik fayl; monkeypatch — env va atributni vaqtincha almashtirish; capsys — chiqishni ushlash; hammasi izolyatsiyalangan."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


KOD = '''
import os

def sozlamani_oqi():
    return os.environ.get("WISAR_REJIM", "ishlab_chiqarish")

def hisobotni_yoz(yol, matn):
    yol.write_text(matn, encoding="utf-8")
    return yol.stat().st_size

def salomlash(ism):
    print(f"Salom, {ism}!")
'''

TEST = '''
from app import sozlamani_oqi, hisobotni_yoz, salomlash

def test_vaqtinchalik_fayl(tmp_path):
    fayl = tmp_path / "hisobot.txt"
    hajm = hisobotni_yoz(fayl, "Wisar hisoboti")
    assert fayl.exists()
    assert fayl.read_text(encoding="utf-8") == "Wisar hisoboti"
    assert hajm > 0

def test_har_test_yangi_papka(tmp_path):
    # bu papka oldingi testnikidan boshqa — izolyatsiya
    assert not (tmp_path / "hisobot.txt").exists()

def test_sukut_rejim():
    assert sozlamani_oqi() == "ishlab_chiqarish"

def test_monkeypatch_env(monkeypatch):
    monkeypatch.setenv("WISAR_REJIM", "test")
    assert sozlamani_oqi() == "test"

def test_env_tiklandi():
    # monkeypatch test tugagach o'zgarishni bekor qildi
    assert sozlamani_oqi() == "ishlab_chiqarish"

def test_chiqishni_ushlash(capsys):
    salomlash("Aziz")
    natija = capsys.readouterr()
    assert natija.out == "Salom, Aziz!\\n"
'''


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

        print("=== 1. O'rnatilgan fixture lar ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"  chiqish kodi: {kod}")
        print("  ⭐ tmp_path izolyatsiya, monkeypatch env ni test tugagach tikladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. O'rnatilgan fixture lar ===
  test_app.py::test_vaqtinchalik_fayl PASSED
  test_app.py::test_har_test_yangi_papka PASSED
  test_app.py::test_sukut_rejim PASSED
  test_app.py::test_monkeypatch_env PASSED
  test_app.py::test_env_tiklandi PASSED
  test_app.py::test_chiqishni_ushlash PASSED
  --- 6 passed ---
  chiqish kodi: 0
  ⭐ tmp_path izolyatsiya, monkeypatch env ni test tugagach tikladi

Nima ko'rsatdi: 2.6-bo'lim.

Misol 4 — Amaliy: conftest.py va baza fixture

Foydalanuvchi repozitoriyni (SQLite, 16.7) test qilamiz. Har testga toza, sinov ma'lumoti bilan to'ldirilgan baza kerak. Bazani ochish, sxema yaratish va tozalash — conftest.py dagi yield fixture da bir marta yoziladi; har test uni parametr sifatida so'rab, o'z izolyatsiyalangan bazasini oladi. Bu — baza kodini test qilishning standart naqshi.

python
"""conftest.py da baza fixture (yield bilan setup/teardown); har test toza baza oladi; fixture kompozitsiyasi (to'ldirilgan baza); repozitoriyni test qilish."""

import re
import subprocess
import sys
import tempfile
from pathlib import Path

KOD = '''
import sqlite3

class FoydalanuvchiRepo:
    def __init__(self, con):
        self.con = con
        con.execute("CREATE TABLE IF NOT EXISTS foydalanuvchi(id INTEGER PRIMARY KEY, login TEXT UNIQUE, faol INTEGER DEFAULT 1)")

    def qosh(self, login):
        cur = self.con.execute("INSERT INTO foydalanuvchi(login) VALUES(?)", (login,))
        return cur.lastrowid

    def ol(self, login):
        r = self.con.execute("SELECT login, faol FROM foydalanuvchi WHERE login=?", (login,)).fetchone()
        return dict(zip(("login", "faol"), r)) if r else None

    def faolsizlantir(self, login):
        cur = self.con.execute("UPDATE foydalanuvchi SET faol=0 WHERE login=?", (login,))
        return cur.rowcount > 0

    def soni(self):
        return self.con.execute("SELECT COUNT(*) FROM foydalanuvchi").fetchone()[0]
'''

CONFTEST = '''
import sqlite3
import pytest
from repo import FoydalanuvchiRepo

@pytest.fixture
def baza():
    con = sqlite3.connect(":memory:")     # SETUP: toza xotira bazasi
    yield con
    con.close()                           # TEARDOWN

@pytest.fixture
def repo(baza):                           # fixture kompozitsiyasi
    return FoydalanuvchiRepo(baza)

@pytest.fixture
def toldirilgan_repo(repo):
    repo.qosh("aziz")
    repo.qosh("bek")
    return repo
'''

TEST = '''
import pytest

def test_bosh_baza_bosh(repo):
    assert repo.soni() == 0

def test_foydalanuvchi_qoshish(repo):
    repo.qosh("aziz")
    assert repo.soni() == 1
    assert repo.ol("aziz") == {"login": "aziz", "faol": 1}

def test_har_test_toza_baza(repo):
    # oldingi test 'aziz' qo'shdi, lekin bu test YANGI, bo'sh baza oladi
    assert repo.soni() == 0

def test_takroriy_login_xato(repo):
    repo.qosh("aziz")
    with pytest.raises(Exception):        # UNIQUE buzilishi
        repo.qosh("aziz")

def test_faolsizlantirish(toldirilgan_repo):
    assert toldirilgan_repo.faolsizlantir("aziz") is True
    assert toldirilgan_repo.ol("aziz")["faol"] == 0

def test_yoq_foydalanuvchi(toldirilgan_repo):
    assert toldirilgan_repo.faolsizlantir("nodira") is False

def test_toldirilgan_baza_soni(toldirilgan_repo):
    assert toldirilgan_repo.soni() == 2
'''


def pytest_ishga(katalog: Path, *args: str) -> tuple[int, list[str]]:
    r = subprocess.run([sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-o", "addopts=", *args],
                       cwd=katalog, capture_output=True, text=True, encoding="utf-8")
    satrlar = []
    for l in r.stdout.splitlines():
        if re.match(r"(=+ test session|collecting|collected|platform |cachedir|rootdir|plugins|configfile|hypothesis profile)", l):
            continue
        l = re.sub(r"\s+\[\s*\d+%\]$", "", l)
        l = re.sub(r" in [\d.]+s\b", "", l)
        l = re.sub(r"^(=+) (.*?) =+$", r"--- \2 ---", l)
        satrlar.append(l.rstrip())
    while satrlar and satrlar[0] == "":
        satrlar.pop(0)
    while satrlar and satrlar[-1] == "":
        satrlar.pop()
    return r.returncode, satrlar


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

        print("=== 1. conftest.py fixture lari bilan testlar ===")
        kod, satrlar = pytest_ishga(r, "-v", "--tb=short")
        for satr in satrlar:
            if "::" in satr or satr.startswith("---"):
                print(f"  {satr}")
        print(f"\n  chiqish kodi: {kod}")
        print("  ⭐ conftest fixture lari import qilinmasdan barcha testga ko'rindi")
        print("  ⭐ har test yield fixture orqali toza, izolyatsiyalangan baza oldi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. conftest.py fixture lari bilan testlar ===
  test_repo.py::test_bosh_baza_bosh PASSED
  test_repo.py::test_foydalanuvchi_qoshish PASSED
  test_repo.py::test_har_test_toza_baza PASSED
  test_repo.py::test_takroriy_login_xato PASSED
  test_repo.py::test_faolsizlantirish PASSED
  test_repo.py::test_yoq_foydalanuvchi PASSED
  test_repo.py::test_toldirilgan_baza_soni PASSED
  --- 7 passed ---

  chiqish kodi: 0
  ⭐ conftest fixture lari import qilinmasdan barcha testga ko'rindi
  ⭐ har test yield fixture orqali toza, izolyatsiyalangan baza oldi

Nima ko'rsatdi: 2.3, 2.5, 2.7-bo'limlar.


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

Noto'g'ri fikr To'g'risi
"Fixture ni qo'lda chaqirish kerak" pytest parametr nomidan o'zi chaqiradi
"Bir fixture barcha testda umumiy nusxa" Sukut function scope — har test yangi
"Tozalash uchun alohida kod kerak" yield fixture teardownni beradi
"yield dan keyingi kod test yiqilsa bajarilmaydi" Bajariladi — try/finally kabi
"Fixture faqat ma'lumot qaytaradi" Ulanish, fayl, holat — istalgan resurs
"conftest.py ni import qilish kerak" pytest uni avtomatik topadi
"scope=session doim tezroq va yaxshiroq" Umumiy holat testlarni bog'lashi mumkin
"monkeypatch o'zgarishini qo'lda tiklash kerak" Test tugagach avtomatik tiklanadi

6. Keng tarqalgan xatolar va yechimlari

1. Tayyorlashni har testda takrorlash

python
def test_a():
    s = Savat(); s.qosh("kitob")        # ❌ takror
    ...
@pytest.fixture
def savat():                            # ✅
    s = Savat(); s.qosh("kitob"); return s

2. Fixture ni funksiya sifatida chaqirish

python
def test_x():
    data = malumot()                    # ❌ fixture funksiya emas
def test_x(malumot):                    # ✅ parametr sifatida
    ...

3. Tozalashni yield siz

python
@pytest.fixture
def baza():
    con = och()
    return con                          # ❌ con.close() hech qachon
@pytest.fixture
def baza():
    con = och(); yield con; con.close() # ✅

4. O'zgartiriladigan ma'lumotni keng scope bilan

python
@pytest.fixture(scope="session")        # ❌ testlar bir-birini buzadi
def royxat(): return []
@pytest.fixture                         # ✅ har test yangi
def royxat(): return []

5. tmp_path o'rniga qattiq kodlangan yo'l

python
def test_fayl():
    Path("/tmp/test.txt").write_text(...)   # ❌ izolyatsiya yo'q, tozalanmaydi
def test_fayl(tmp_path):                    # ✅
    (tmp_path / "test.txt").write_text(...)

6. os.environ ni to'g'ridan-to'g'ri o'zgartirish

python
def test_x():
    os.environ["REJIM"] = "test"        # ❌ keyingi testga ta'sir qiladi
def test_x(monkeypatch):                # ✅ avtomatik tiklanadi
    monkeypatch.setenv("REJIM", "test")

7. Fixture ni conftest.py o'rniga import qilish

python
from test_savat import savat            # ❌ testni import qilish
# ✅ conftest.py ga qo'ying — pytest avtomatik topadi

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 13.5-dars (o'tilgan): dependency injection — fixture aynan shu
  • 16.7-dars (o'tilgan): sqlite3 — baza fixture
  • 17.4-dars: parametrize — fixture bilan birga ishlatiladi
  • 17.5–17.6-darslar: mock — monkeypatch ning kuchliroq versiyasi
  • 20.7-dars: FastAPI Depends — fixture ga o'xshash mexanizm
  • 20.15-dars: FastAPI ilovasini test qilish — fixture bilan

8. Eng yaxshi amaliyotlar

  1. Takroriy tayyorlashni fixture ga chiqaring.

  2. Resurslar (fayl, ulanish) uchun yield fixture — teardown bilan.

  3. Mustaqillik uchun sukut function scope da qoldiring.

  4. Umumiy fixture larni conftest.py ga joylang.

  5. Vaqtinchalik fayllar uchun tmp_path, env/atribut uchun monkeypatch.

  6. Murakkab tayyorlashni fixture kompozitsiyasi bilan qismlarga bo'ling.

  7. autouse ni ehtiyotkorlik bilan — yashirin xatti-harakat.

  8. Fixture nomlari tavsiflovchi bo'lsin: bosh_savat, toldirilgan_repo.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

Har test uchun PASSED yoki FAILED ni ayting (fixture xatti-harakatiga qarab):

python
import pytest

@pytest.fixture
def royxat():
    return [1, 2, 3]

HODISALAR = []

@pytest.fixture
def kuzatuvchi():
    HODISALAR.append("setup")
    yield "x"
    HODISALAR.append("teardown")

1.  def test_a(royxat): assert sum(royxat) == 6
2.  def test_b(royxat):
        royxat.append(4)
        assert len(royxat) == 4
3.  def test_c(royxat): assert len(royxat) == 3
4.  def test_d(kuzatuvchi): assert kuzatuvchi == "x"
5.  def test_e(): assert HODISALAR == ["setup", "teardown"]
6.  def test_f(tmp_path):
        (tmp_path / "a.txt").write_text("x")
        assert (tmp_path / "a.txt").exists()
7.  def test_g(tmp_path): assert not (tmp_path / "a.txt").exists()
8.  def test_h(monkeypatch):
        monkeypatch.setenv("X", "1")
        import os; assert os.environ["X"] == "1"
9.  def test_i():
        import os; assert "X" not in os.environ
10. def test_j(capsys):
        print("salom")
        assert capsys.readouterr().out == "salom\n"
Javoblar
  1. PASSED
  2. PASSED
  3. PASSED — har test yangi ro'yxat oladi (2-test ta'sir qilmaydi)
  4. PASSED
  5. PASSED — 4-test tugagach teardown bajarildi
  6. PASSED
  7. PASSED — har test yangi tmp_path
  8. PASSED
  9. PASSED — monkeypatch env ni tikladi
  10. PASSED

Vazifa 2: Xatolarni tuzating

python
1.  def test_x():
        data = malumot_fixture()            # fixture ni chaqirish
        assert data["nom"] == "test"

2.  @pytest.fixture
    def baza():
        con = sqlite3.connect(":memory:")
        return con                          # tozalash yo'q

3.  @pytest.fixture(scope="session")
    def savat():
        return []                           # o'zgartiriladigan, umumiy

4.  def test_fayl():
        with open("/tmp/hisobot.txt", "w") as f:
            f.write("x")
        assert os.path.exists("/tmp/hisobot.txt")

5.  def test_env():
        os.environ["REJIM"] = "test"
        assert sozlama() == "test"
Javoblar
python
1.  def test_x(malumot_fixture):            # parametr sifatida
        assert malumot_fixture["nom"] == "test"

2.  @pytest.fixture
    def baza():
        con = sqlite3.connect(":memory:")
        yield con                           # yield + teardown
        con.close()

3.  @pytest.fixture                         # sukut function scope
    def savat():
        return []

4.  def test_fayl(tmp_path):                # tmp_path
        fayl = tmp_path / "hisobot.txt"
        fayl.write_text("x")
        assert fayl.exists()

5.  def test_env(monkeypatch):              # monkeypatch
        monkeypatch.setenv("REJIM", "test")
        assert sozlama() == "test"

Vazifa 3: Fixture to'plami

Blog ilovasi uchun (post, izoh, foydalanuvchi):

  1. conftest.py da foydalanuvchi, post, izohli_post fixture lari (kompozitsiya)
  2. Baza yield fixture bilan (SQLite :memory:)
  3. Har test toza baza olishini isbotlovchi test
  4. pytest -v da barcha o'tsin

Vazifa 4: monkeypatch bilan izolyatsiya

Tashqi API ga murojaat qiladigan funksiyani test qiling:

  1. monkeypatch.setattr bilan tarmoq chaqiruvini soxta funksiyaga almashtiring
  2. Turli javoblar (muvaffaqiyat, xato, timeout) uchun testlar
  3. monkeypatch.setenv bilan API kalitini sozlang
  4. Haqiqiy tarmoqqa chiqmasligini ta'minlang

Vazifa 5: Scope tajribasi

  1. function, module, session scope li fixture lar yarating
  2. Har biri necha marta chaqirilishini sanang (global hisoblagich)
  3. pytest --setup-show bilan chaqiruv tartibini kuzating
  4. Qaysi holatda qaysi scope to'g'ri kelishini yozing

Vazifa 6: autouse va tozalash

  1. autouse=True fixture bilan har testdan oldin global holatni tiklang
  2. Uning yashirin ekanini va testni tushunishga ta'sirini muhokama qiling
  3. Aniq (so'raladigan) fixture bilan solishtiring
  4. Qaysi biri afzalroq — xulosa

Vazifa 7: O'ylash

Fixture — pytest ning eng o'ziga xos va boshqa til/vositalarda kam uchraydigan xususiyati. unittest da setUp/tearDown metodlari bor edi — nima uchun pytest ularni fixture bilan almashtirdi, va fixture ning "so'rov bo'yicha in'eksiya" modeli setUp dan nimasi bilan yaxshiroq?

Javob

Qisqa javob: setUp/tearDown har testdan oldin/keyin bir xil ishni bajaradi — test nima kerakligini tanlay olmaydi, hammasi tayyorlanadi. Fixture esa so'rov bo'yicha: test faqat kerakli fixture larni parametr sifatida so'raydi, pytest ularni (va ular bog'liq bo'lgan boshqa fixture larni) tayyorlaydi. Bu modullilik, qayta ishlatish va aniqlik beradi — har testning nimaga bog'liqligi uning imzosida ko'rinadi.

1. setUp/tearDown cheklovlari

Muammo Tafsilot
Hammasi yoki hech narsa Har test butun setUp ni oladi, kerak-kermas
Modullik yo'q Bitta katta setUp, qismlarga bo'linmaydi
Qayta ishlatish qiyin Boshqa sinfda takrorlash kerak
Bog'liqlik ko'rinmaydi Test nimaga tayangani noaniq
Scope cheklangan Faqat sinf darajasida

2. Fixture afzalliklari

Xususiyat Foyda
So'rov bo'yicha Test faqat kerakligini oladi
Bog'liqlik imzoda def test_x(baza, repo) — aniq
Kompozitsiya Fixture fixture ni ishlatadi
Qayta ishlatish conftest.py — hamma joyda
Moslashuvchan scope function/class/module/session
Avtomatik teardown yield bilan

3. "So'rov bo'yicha in'eksiya" nima beradi

Bu — dependency injection naqshi 13.5-bob. Test o'z bog'liqliklarini e'lon qiladi, tizim ularni yetkazadi. Natijada:

  • Test o'qilishi oson (nimaga bog'liq — ko'rinadi)
  • Testlar tez (faqat kerakli tayyorlanadi)
  • Refaktoring oson (fixture bir joyda o'zgaradi)

4. Narxi

Kamchilik Izoh
O'rganish egri chizig'i "Sehr" kabi ko'rinadi boshda
Yashirin bog'liqlik (autouse) Ehtiyot bo'lish kerak
Nomlash muhim Fixture nomi ma'noli bo'lsin

5. Muhandislik saboqlari

  1. So'rov bo'yicha (explicit) model hammasi-yoki-hech-narsadan yaxshi
  2. Bog'liqlikni ko'rinadigan qilish — test va kodda foydali
  3. Kompozitsiya — kichik qismlardan murakkab tuzilma
  4. DI naqshi test'dan tashqarida ham (FastAPI, 20.7) qo'llaniladi

6. Xulosa

  1. setUp hammani tayyorlaydi; fixture — so'rov bo'yicha
  2. Bog'liqlik test imzosida ko'rinadi
  3. Kompozitsiya va qayta ishlatish — fixture kuchi
  4. Bu dependency injection naqshining test'dagi ko'rinishi

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda fixture larni — testga kerak narsani tayyorlab, tozalab beruvchi mexanizmni o'rgandik.

Eng muhim uch fikr:

  1. Fixture takrorni yo'q qiladi va mustaqillik beradi. @pytest.fixture bilan belgilangan funksiya testga kerakli narsani (obyekt, ma'lumot, ulanish) tayyorlaydi; test uni parametr nomi orqali so'raydi va pytest avtomatik chaqiradi — bu dependency injection. Sukut function scope da har test yangi, toza nusxa oladi, shuning uchun testlar bir-biriga ta'sir qilmaydi.

  2. yield fixture — setup va teardown. yield gacha tayyorlash, keyin tozalash bajariladi — va tozalash test yiqilsa ham ishlaydi (try/finally kabi). Bu fayl, ulanish, server kabi resurslarni to'g'ri boshqarishning standart usuli. Scope (function/class/module/session) fixture qancha marta chaqirilishini belgilaydi — kengroq tezroq, lekin o'zgartiriladigan holatda ehtiyot bo'ling.

  3. O'rnatilgan fixture lar va conftest.py. tmp_path (izolyatsiyalangan vaqtinchalik fayl), monkeypatch (env/atributni vaqtincha almashtirish, avtomatik tiklash), capsys (chiqishni ushlash) — tayyor, kuchli vositalar. Bir necha faylda ishlatiladigan fixture larni conftest.py ga joylang — pytest uni import qilmasdan avtomatik topadi. Murakkab tayyorlashni fixture kompozitsiyasi bilan kichik qismlarga bo'ling.

Keyingi darsda parametrize ni o'rganamiz: bitta testni ko'p xil kirish-natija juftligi bilan ishga tushirib, o'nlab holatni qisqa, takrorsiz kod bilan qamrab olish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

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