Python’da pytest bilan testlash: fixture, parametrize va mocklar
Amaliyot3 daqInnovialabs tahririyati
Qisqacha
pytest testlari oddiy assert ishlatadigan test_* funksiyalaridir; fixture ma’lumot va resurslarni tayyorlaydi, parametrize bitta testni ko‘p kirish ma’lumotlarida ishlatadi, mocklar esa tarmoq va tashqi servislarni almashtiradi.
Avval bilish kerak bo‘lganlar
pytest test_*.py fayllarini topadi, ulardagi test_* funksiyalarini ishga tushiradi va oddiy assert larni tekshiradi. Meros olinadigan klasslar yoki maxsus solishtirish metodlari kerak emas. Buning ustiga uchta asosiy vosita bor:
- fixture — testlar uchun ma’lumot va resurslarni tayyorlash;
- parametrize — bitta test ko‘plab kirish ma’lumotlari to‘plamida;
- mocklar — tarmoq, vaqt va tashqi servislarni almashtirish.
O‘rnatish va birinchi test
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install pytest
Kod va test:
# app/pricing.py
def apply_discount(price, percent):
if not 0 <= percent <= 100:
raise ValueError("percent must be 0..100")
return round(price * (100 - percent) / 100, 2)
# tests/test_pricing.py
import pytest
from app.pricing import apply_discount
def test_discount_basic():
assert apply_discount(200, 10) == 180
def test_discount_invalid_percent():
with pytest.raises(ValueError):
apply_discount(100, 150)
Loyiha ildizida pytest ni ishga tushiring. Foydali flaglar: -v — batafsil chiqish, -x — birinchi xatoda to‘xtash, -k discount — nomining bir qismi bo‘yicha testlarni ishga tushirish. Test yiqilganda pytest assert ning ikkala tomonidagi qiymatlarni ko‘rsatadi, bu esa debuggingni osonlashtiradi.
Fixture’lar
Fixture — @pytest.fixture bilan belgilangan funksiya, pytest uning natijasini argument nomi bo‘yicha testga uzatadi.
import pytest
@pytest.fixture
def cart():
return {"items": [], "currency": "UZS"}
def test_empty_cart(cart):
assert cart["items"] == []
Agar resursni testdan keyin yopish kerak bo‘lsa, yield dan foydalaning: undan keyingi hamma narsa teardown sifatida bajariladi.
@pytest.fixture
def db_connection():
conn = connect_test_db()
yield conn
conn.close()
scope parametri yashash muddatini belgilaydi: function (standart, har bir test uchun yangi fixture), module, session. Test bazasi kabi qimmat resurslarni sessiyaga bir marta yaratish qulay, lekin unda testlar umumiy ma’lumotni buzmasligini kuzating.
Bilish kerak bo‘lgan o‘rnatilgan fixture’lar: tmp_path — vaqtinchalik papka, monkeypatch — atributlar va muhit o‘zgaruvchilarini almashtirish, capsys — chiqishni ushlab qolish.
conftest.py
Bir nechta faylda kerak bo‘ladigan fixture’larni conftest.py ga joylang. pytest uni avtomatik yuklaydi — hech narsani import qilish shart emas. tests/ papkasidagi conftest.py undagi barcha testlarga ko‘rinadi, ichki papkadagisi esa faqat o‘sha papka testlariga.
Parametrlash
O‘nta deyarli bir xil test o‘rniga bittasi:
@pytest.mark.parametrize(
"price, percent, expected",
[
(100, 0, 100),
(100, 25, 75),
(99.99, 10, 89.99),
(100, 100, 0),
],
)
def test_discount(price, percent, expected):
assert apply_discount(price, percent) == expected
Har bir to‘plam hisobotda alohida test sifatida ko‘rinadi va qaysi holat yiqilgani darhol bilinadi. O‘qilishi oson nomlar uchun ids=[...] uzating.
HTTP so‘rovlarni mock qilish
Testlar haqiqiy tarmoqqa chiqmasligi kerak: bu sekin, beqaror va boshqalarning ma’lumotini o‘zgartirishi mumkin. Chaqiruvni o‘z kodingiz darajasida almashtiring:
# app/rates.py
import requests
def get_usd_rate():
resp = requests.get("https://api.example.com/rates", timeout=5)
resp.raise_for_status()
return resp.json()["USD"]
# tests/test_rates.py
from unittest.mock import Mock
from app import rates
def test_get_usd_rate(monkeypatch):
fake = Mock()
fake.json.return_value = {"USD": 12650.0}
fake.raise_for_status.return_value = None
monkeypatch.setattr(rates.requests, "get", Mock(return_value=fake))
assert rates.get_usd_rate() == 12650.0
Asosiy qoida: obyektni u ishlatiladigan joyda patch qiling, aniqlangan joyda emas. HTTP’ni qulay mock qilish uchun maxsus kutubxonalar bor, masalan requests uchun responses yoki httpx uchun respx — ular soxta javoblarni deklarativ tasvirlash imkonini beradi.
Haqiqiy loyihada testlar tuzilmasi
project/
├── app/
│ ├── pricing.py
│ └── rates.py
├── tests/
│ ├── conftest.py
│ ├── unit/
│ │ └── test_pricing.py
│ └── integration/
│ └── test_rates_api.py
└── pyproject.toml
Sozlamalar pyproject.toml da:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
markers = ["integration: tests that need external services"]
Sekin testlarni @pytest.mark.integration bilan belgilang va alohida ishga tushiring: pytest -m "not integration".
Ko‘p uchraydigan xatolar
- Testlar ishga tushirish tartibiga yoki umumiy holatga bog‘liq.
- Mocklar juda ko‘p narsani almashtiradi — test kodni emas, mockni tekshiradi.
floatqiymatlarinipytest.approxo‘rniga==bilan solishtirish.- Bitta test beshta turli narsani tekshiradi — yiqilganda nima buzilgani tushunarsiz.