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.
Ushbu bo‘lim mundarijasi
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:
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())
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:
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
pytest -q test_fixture.py
.. [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.
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:
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):
pytest -q -s test_korish.py
>> 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:
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
pytest -q -s test_yield.py
>> 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:
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
pytest -q -s --tb=line test_yield.py
>> 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.
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:
| Qamrov | Fixture necha marta ishlaydi |
|---|---|
function (sukut bo'yicha) | Har test uchun bir marta |
class | Har test sinfi uchun bir marta |
module | Har fayl uchun bir marta |
package | Har paket uchun bir marta |
session | Butun ishga tushirish uchun bir marta |
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"
pytest -q -s test_qamrov.py
>> QIMMAT tayyorgarlik
>> arzon tayyorgarlik
. >> arzon tayyorgarlik
. >> arzon tayyorgarlik
.
3 passed in 0.01s
Qimmat tayyorgarlik bir marta, arzoni esa uch marta ishladi.
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:
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.
def test_bosh_savat_jamisi_nol(bosh_savat):
assert bosh_savat.jami() == 0
def test_toldirilgan_savat_jamisi(toldirilgan_savat):
assert toldirilgan_savat.jami() == 29000
pytest -q test_birinchi_modul.py test_ikkinchi_modul.py
.. [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:
| Fixture | Nima beradi |
|---|---|
tmp_path | Vaqtinchalik papka (Path obyekti), o'zi tozalanadi |
tmp_path_factory | Sessiya davomida yashaydigan vaqtinchalik papka |
capsys | Chop etilgan matnni ushlaydi |
monkeypatch | Qiymat va muhit o'zgaruvchisini vaqtincha almashtiradi |
caplog | Jurnal (logging) xabarlarini ushlaydi |
request | Joriy test haqida ma'lumot |
Ikkitasini amalda ko'ramiz:
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}!")
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"
pytest -q test_tayyor.py
.. [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:
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
pytest -q test_fabrika.py
.. [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.
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:
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.
- Takrorlanadigan uchta qatorli ikkita testni fixture ga o'tkazing.
- Fixture ichiga
printqo'yib,-sbilan necha marta ishlashini ko'ring. yieldli fixture yozing va tozalash qachon bajarilishini kuzating.- Testni ataylab yiqiting - tozalash baribir bajarildimi?
scope="module"qo'yib, chiqishning qanday o'zgarishini yozing.- O'zgaradigan obyektga keng qamrov berib, ikkinchi testni yiqiting.
conftest.pyyarating va ikkita fayldan bitta fixture ni ishlating.tmp_pathbilan fayl yozib, uning test tugagach yo'qolishini tekshiring.capsysbilan chop etilgan matnni ushlang.- 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.
returno'rnigayieldyozilsa, undan keyingi kod tozalash sifatida test tugagach bajariladi.- Tozalash test yiqilsa ham bajariladi; bir necha fixture bo'lsa, teskari tartibda yuradi.
scopefixture 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.pydagi 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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.