Asosiy kontentga o‘tish
Innovialabs

Python’da pytest bilan testlash: fixture, parametrize va mocklar

Amaliyot3 daq

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.
  • float qiymatlarini pytest.approx o‘rniga == bilan solishtirish.
  • Bitta test beshta turli narsani tekshiradi — yiqilganda nima buzilgani tushunarsiz.

Ko‘p beriladigan savollar

pytest unittest testlarini ham ishga tushiradi, lekin yozish qisqaroq: oddiy assert, setUp o‘rniga fixture’lar, tayyor parametrlash va boy plaginlar ekotizimi.

Yana o‘qing

G‘oya yoki vazifangiz bormi? Bizga aytib bering

Ish kuni davomida javob beramiz va yechim taklif qilamiz. Konsultatsiya bepul.