7-bo‘lim

Fixture va test ma'lumotlari

Takrorlanadigan tayyorgarlikni fixture ga chiqarish, yield bilan tozalash, qamrov (scope) tanlash, conftest.py va pytest ning tayyor fixture'lari.

🕑 18 daqiqa o‘qish 📄 918 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Takrorlanishdan fixture ga
  2. Tozalash: yield
  3. Qamrov: fixture necha marta ishlasin
  4. conftest.py - fixture'larni ulashish
  5. pytest ning tayyor fixture'lari
  6. Fixture ma'lumot emas, fabrika qaytarsin
  7. Xulosa

Oldingi bo'limlardagi testlarning boshida bir xil qatorlar takrorlanib turgan edi: savat = Savat(), keyin ikkita qosh(...).

Fixture - ana shu takrorlanadigan tayyorgarlikni bir joyga yig'ish usuli.

Takrorlanishdan fixture ga #

Avvalgi holat:

Python
class Savat:
    def __init__(self):
        self.mahsulotlar = {}

    def qosh(self, nom, narx, soni=1):
        if soni < 1:
            raise ValueError("soni kamida 1 bo'lishi kerak")
        mavjud = self.mahsulotlar.get(nom, {"narx": narx, "soni": 0})
        mavjud["soni"] += soni
        self.mahsulotlar[nom] = mavjud

    def ochir(self, nom):
        del self.mahsulotlar[nom]

    def jami(self):
        return sum(m["narx"] * m["soni"] for m in self.mahsulotlar.values())
Python
from savat import Savat


def test_jami_hisoblanadi():
    savat = Savat()
    savat.qosh("non", 5000)
    savat.qosh("sut", 12000, 2)

    assert savat.jami() == 29000


def test_mahsulot_ochiriladi():
    savat = Savat()
    savat.qosh("non", 5000)
    savat.qosh("sut", 12000, 2)

    savat.ochir("non")

    assert savat.jami() == 24000

Uch qator ikki marta takrorlandi. Fixture bilan:

Python
import pytest

from savat import Savat


@pytest.fixture
def toldirilgan_savat():
    savat = Savat()
    savat.qosh("non", 5000)
    savat.qosh("sut", 12000, 2)
    return savat


def test_jami_hisoblanadi(toldirilgan_savat):
    assert toldirilgan_savat.jami() == 29000


def test_mahsulot_ochiriladi(toldirilgan_savat):
    toldirilgan_savat.ochir("non")

    assert toldirilgan_savat.jami() == 24000
Terminal
pytest -q test_fixture.py
Natija
..                                                               [100%]
2 passed in 0.01s

Diqqat qiling: testlar fixture ni nomi bo'yicha so'radi. Argument nomi fixture nomi bilan bir xil bo'lsa, pytest uni o'zi topib beradi. Bu usul bog'liqlikni kiritish (dependency injection) deb ataladi.

Fixture har test uchun qaytadan bajariladi

Yuqoridagi ikkita test bitta toldirilgan_savat ni ulashmaydi. pytest har test uchun funksiyani qaytadan chaqiradi va yangi obyekt yaratadi.

Aynan shuning uchun 5-bo'limdagi "umumiy holat" muammosi bu yerda takrorlanmaydi: ikkinchi test birinchisining o'zgarishlarini ko'rmaydi.

Buni o'z ko'zimiz bilan ko'ramiz. Fixture ichiga chop etish qo'shamiz:

Python
import pytest


@pytest.fixture
def raqam():
    print("\n  >> fixture ishladi")
    return 42


def test_birinchi(raqam):
    print("  >> birinchi test")
    assert raqam == 42


def test_ikkinchi(raqam):
    print("  >> ikkinchi test")
    assert raqam == 42

-s bayrog'i chop etilganlarni ko'rsatadi (odatda pytest ularni yutadi):

Terminal
pytest -q -s test_korish.py
Natija

  >> fixture ishladi
  >> birinchi test
.
  >> fixture ishladi
  >> ikkinchi test
.
2 passed in 0.00s

Fixture ikki marta ishladi - har test uchun bittadan.

Tozalash: yield #

Ba'zi tayyorgarliklar ortidan tozalash kerak bo'ladi: fayl yopish, ulanish uzish, vaqtinchalik papkani o'chirish. return o'rniga yield yozilsa, undan keyingi kod test tugagandan so'ng bajariladi:

Python
import pytest


class SoxtaUlanish:
    def __init__(self):
        self.ochiq = True
        print("\n  >> ulanish ochildi")

    def yop(self):
        self.ochiq = False
        print("  >> ulanish yopildi")


@pytest.fixture
def ulanish():
    aloqa = SoxtaUlanish()
    yield aloqa
    aloqa.yop()


def test_ulanish_ochiq(ulanish):
    print("  >> test ishlayapti")
    assert ulanish.ochiq is True
Terminal
pytest -q -s test_yield.py
Natija

  >> ulanish ochildi
  >> test ishlayapti
.  >> ulanish yopildi

1 passed in 0.00s

Tartibga e'tibor bering: ochildi → test → yopildi.

Endi eng muhim xususiyatni tekshiramiz - testni ataylab yiqitamiz:

Python
import pytest


class SoxtaUlanish:
    def __init__(self):
        self.ochiq = True
        print("\n  >> ulanish ochildi")

    def yop(self):
        self.ochiq = False
        print("  >> ulanish yopildi")


@pytest.fixture
def ulanish():
    aloqa = SoxtaUlanish()
    yield aloqa
    aloqa.yop()


def test_ulanish_ochiq(ulanish):
    print("  >> test ishlayapti")
    assert ulanish.ochiq is False
Terminal
pytest -q -s --tb=line test_yield.py
Natija

  >> ulanish ochildi
  >> test ishlayapti
F  >> ulanish yopildi

=============================== FAILURES ===============================
E   assert True is False
     +  where True = <test_yield.SoxtaUlanish object at 0x78ab0fa86120>.ochiq
/home/dasturchi/loyiha/test_yield.py:23: assert True is False
======================= short test summary info ========================
FAILED test_yield.py::test_ulanish_ochiq - assert True is False
1 failed in 0.00s

Test yiqildi, lekin ulanish yopildi qatori baribir chiqdi. Bu muhim: aks holda yiqilgan bitta test ortidan ochiq ulanish yoki qulflangan fayl qolib ketardi va keyingi testlar ham yiqila boshlardi.

yield bilan yozilgan fixture'ning hayoti yield gacha Tayyorgarlik bajariladi Test ishlaydi yield qaytargan qiymat bilan yield dan keyin Tozalash bajariladi Test yiqilsa ham, istisno tashlasa ham - tozalash baribir bajariladi Bir necha fixture bo'lsa, tozalash teskari tartibda yuradi - oxirgi ochilgani birinchi yopiladi
Tayyorgarlik va tozalash bitta funksiyada yonma-yon turadi

Qamrov: fixture necha marta ishlasin #

Ba'zi tayyorgarliklar qimmat bo'ladi - masalan katta fayl o'qish yoki server ko'tarish. Ularni har test uchun qaytadan bajarish isrof. scope shuni boshqaradi:

QamrovFixture necha marta ishlaydi
function (sukut bo'yicha)Har test uchun bir marta
classHar test sinfi uchun bir marta
moduleHar fayl uchun bir marta
packageHar paket uchun bir marta
sessionButun ishga tushirish uchun bir marta
Python
import pytest


@pytest.fixture(scope="module")
def qimmat_tayyorgarlik():
    print("\n  >> QIMMAT tayyorgarlik")
    return {"holat": "tayyor"}


@pytest.fixture
def arzon_tayyorgarlik():
    print("  >> arzon tayyorgarlik")
    return []


def test_birinchi(qimmat_tayyorgarlik, arzon_tayyorgarlik):
    assert qimmat_tayyorgarlik["holat"] == "tayyor"


def test_ikkinchi(qimmat_tayyorgarlik, arzon_tayyorgarlik):
    assert arzon_tayyorgarlik == []


def test_uchinchi(qimmat_tayyorgarlik, arzon_tayyorgarlik):
    assert qimmat_tayyorgarlik["holat"] == "tayyor"
Terminal
pytest -q -s test_qamrov.py
Natija

  >> QIMMAT tayyorgarlik
  >> arzon tayyorgarlik
.  >> arzon tayyorgarlik
.  >> arzon tayyorgarlik
.
3 passed in 0.01s

Qimmat tayyorgarlik bir marta, arzoni esa uch marta ishladi.

Keng qamrov - umumiy holat demakdir

scope="module" yoki scope="session" qo'ygan zahotingiz siz 5-bo'limdagi muammoni qaytarib olasiz: testlar bitta obyektni ulashadi.

Agar test o'sha obyektni o'zgartirsa, keyingi testlar o'zgargan holatni ko'radi.

Xavfsiz qoida:

  • o'zgarmaydigan narsalar (sozlamalar, o'qilgan fayl, ulanish) - keng qamrov mumkin;
  • o'zgaradigan narsalar (savat, ro'yxat, ma'lumot yozuvi) - faqat function.

Shubha bo'lsa, function ni tanlang. U eng sekin, lekin eng xavfsiz.

conftest.py - fixture'larni ulashish #

Fixture bir necha faylda kerak bo'lsa, uni conftest.py ga chiqaring. pytest bu faylni avtomatik topadi - import qilish shart emas:

Python
import pytest

from savat import Savat


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


@pytest.fixture
def toldirilgan_savat(bosh_savat):
    bosh_savat.qosh("non", 5000)
    bosh_savat.qosh("sut", 12000, 2)
    return bosh_savat

E'tibor bering: ikkinchi fixture birinchisini argument sifatida oldi. Fixture'lar bir-birini xuddi testlar kabi chaqira oladi.

Python
def test_bosh_savat_jamisi_nol(bosh_savat):
    assert bosh_savat.jami() == 0
Python
def test_toldirilgan_savat_jamisi(toldirilgan_savat):
    assert toldirilgan_savat.jami() == 29000
Terminal
pytest -q test_birinchi_modul.py test_ikkinchi_modul.py
Natija
..                                                               [100%]
2 passed in 0.01s

Ikkala faylda ham hech qanday import yo'q, lekin fixture'lar ishladi.

pytest ning tayyor fixture'lari #

Bir nechta fixture pytest bilan birga keladi - ularni yozish shart emas:

FixtureNima beradi
tmp_pathVaqtinchalik papka (Path obyekti), o'zi tozalanadi
tmp_path_factorySessiya davomida yashaydigan vaqtinchalik papka
capsysChop etilgan matnni ushlaydi
monkeypatchQiymat va muhit o'zgaruvchisini vaqtincha almashtiradi
caplogJurnal (logging) xabarlarini ushlaydi
requestJoriy test haqida ma'lumot

Ikkitasini amalda ko'ramiz:

Python
def faylga_yoz(yol, qatorlar):
    with open(yol, "w", encoding="utf-8") as fayl:
        for qator in qatorlar:
            fayl.write(qator + "\n")
    return len(qatorlar)


def salomlash(ism):
    print(f"Assalomu alaykum, {ism}!")
Python
from hisobot import faylga_yoz, salomlash


def test_fayl_yoziladi(tmp_path):
    yol = tmp_path / "hisobot.txt"

    soni = faylga_yoz(yol, ["birinchi", "ikkinchi"])

    assert soni == 2
    assert yol.read_text(encoding="utf-8") == "birinchi\nikkinchi\n"


def test_chop_etilgan_matn(capsys):
    salomlash("Aziza")

    chiqish = capsys.readouterr()
    assert chiqish.out == "Assalomu alaykum, Aziza!\n"
Terminal
pytest -q test_tayyor.py
Natija
..                                                               [100%]
2 passed in 0.01s

tmp_path har test uchun yangi papka beradi va uni keyin o'zi tozalaydi - testlar bir-birining fayllarini ko'rmaydi.

Fixture ma'lumot emas, fabrika qaytarsin #

Ba'zan har test o'ziga moslangan ma'lumot xohlaydi. Fixture qiymat emas, funksiya qaytarishi mumkin:

Python
import pytest

from savat import Savat


@pytest.fixture
def savat_yasa():
    def yasovchi(*mahsulotlar):
        savat = Savat()
        for nom, narx, soni in mahsulotlar:
            savat.qosh(nom, narx, soni)
        return savat
    return yasovchi


def test_bitta_mahsulot(savat_yasa):
    savat = savat_yasa(("non", 5000, 1))

    assert savat.jami() == 5000


def test_uchta_mahsulot(savat_yasa):
    savat = savat_yasa(("non", 5000, 2), ("sut", 12000, 1), ("yog", 30000, 1))

    assert savat.jami() == 52000
Terminal
pytest -q test_fabrika.py
Natija
..                                                               [100%]
2 passed in 0.01s

Bu naqsh fabrika fixture deb ataladi. U katta loyihalarda eng ko'p ishlatiladigan shakllardan biri: har test kerakli ma'lumotni o'zi buyurtma qiladi, lekin yaratish mantig'i bir joyda turadi.

Test ma'lumotida faqat muhim narsani ko'rsating

Foydalanuvchi obyektida yigirmata maydon bo'lsa ham, test uchun ulardan biri muhim bo'ladi. Fabrika fixture'ga sukut qiymatlarni qo'ying va testda faqat farqni yozing:

Python
foydalanuvchi = foydalanuvchi_yasa(yosh=17)   # qolgan hammasi sukut bo'yicha

Shunda testni o'qigan odam darhol tushunadi: bu test yosh haqida. Agar u yerda yigirmata maydon yozilgan bo'lsa, qaysi biri muhimligini topish uchun kodni o'qish kerak bo'ladi.

Katta loyihalarda bu ishni factory-boy yoki Faker kutubxonalari yengillashtiradi - lekin oddiy funksiya ham to'liq yetadi.

Amaliy topshiriq
  1. Takrorlanadigan uchta qatorli ikkita testni fixture ga o'tkazing.
  2. Fixture ichiga print qo'yib, -s bilan necha marta ishlashini ko'ring.
  3. yield li fixture yozing va tozalash qachon bajarilishini kuzating.
  4. Testni ataylab yiqiting - tozalash baribir bajarildimi?
  5. scope="module" qo'yib, chiqishning qanday o'zgarishini yozing.
  6. O'zgaradigan obyektga keng qamrov berib, ikkinchi testni yiqiting.
  7. conftest.py yarating va ikkita fayldan bitta fixture ni ishlating.
  8. tmp_path bilan fayl yozib, uning test tugagach yo'qolishini tekshiring.
  9. capsys bilan chop etilgan matnni ushlang.
  10. Fabrika fixture yozing va uni uchta turli test ichida ishlating.

Xulosa #

  • Fixture - testlar uchun takrorlanadigan tayyorgarlikni bir joyga yig'ish usuli.
  • Test fixture ni argument nomi orqali so'raydi; pytest uni o'zi topib beradi.
  • Bu usul bog'liqlikni kiritish (dependency injection) deb ataladi.
  • Sukut bo'yicha fixture har test uchun qaytadan bajariladi - shuning uchun testlar bir-biriga ta'sir qilmaydi.
  • return o'rniga yield yozilsa, undan keyingi kod tozalash sifatida test tugagach bajariladi.
  • Tozalash test yiqilsa ham bajariladi; bir necha fixture bo'lsa, teskari tartibda yuradi.
  • scope fixture necha marta ishlashini belgilaydi: function, class, module, package, session.
  • Keng qamrov tezlik beradi, lekin umumiy holat muammosini qaytaradi.
  • O'zgarmaydigan narsalarga keng qamrov mumkin; o'zgaradiganlarga faqat function.
  • conftest.py dagi fixture'lar barcha test fayllariga import qilinmasdan ko'rinadi.
  • Fixture boshqa fixture ni argument sifatida olishi mumkin.
  • pytest tayyor fixture'lar beradi: tmp_path, capsys, monkeypatch, caplog, tmp_path_factory.
  • Fabrika fixture qiymat emas, funksiya qaytaradi - har test o'z ma'lumotini buyurtma qiladi.
  • Test ma'lumotida faqat testga tegishli maydonni ko'rsating, qolganini sukut qiymatda qoldiring.

Keyingi bo'limda tashqi dunyoni almashtirishni o'rganamiz: stub, mock va fake nima va ular qachon zarar keltiradi.

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.