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

17.6-dars: Mock — amaliyot

17-QISM — TESTLASH · 6-dars


1. Kirish va motivatsiya

O'tgan darsda mock tushunchasini o'rgandik: tashqi bog'liqlikni soxta obyekt bilan almashtirish. U yerda bog'liqlikni in'eksiya qildik — funksiyaga parametr sifatida uzatib. Lekin real kod ko'pincha bog'liqlikni to'g'ridan-to'g'ri chaqiradi: requests.get(...), datetime.now(), open(...). Bularni in'eksiya qilinmagan bo'lsa qanday almashtirasiz? Javob — patch: mavjud koddagi obyektni test davomida vaqtincha soxta bilan almashtirish.

patch — mock ning eng amaliy vositasi, lekin unda bitta mashhur tuzoq bor: qayerda patch qilish kerak. Bu tuzoqni tushunmaslik — mock bilan bog'liq eng ko'p uchraydigan xato.

Real vaziyat. Bir jamoa ob-havo funksiyasini test qilardi. Funksiya from ob_havo_api import haroratni_ol qilib, keyin haroratni_ol(shahar) chaqirardi. Ular patch("ob_havo_api.haroratni_ol", return_value=25) yozishdi — lekin test haqiqiy tarmoqqa chiqishda davom etdi va beqaror bo'ldi. Sabab: modul allaqachon haroratni_ol ni o'z nomlar fazosiga import qilgan, va patch asl joyni o'zgartirganda ular kutgan nusxaga ta'sir qilmadi. Yechim: patch("ular_moduli.haroratni_ol", ...) — qayerda ishlatilsa, o'sha yerda patch qilish. Bu bitta qoidani anglash mock bilan ishlashni butunlay o'zgartirdi.

Bu darsda patch bilan mavjud koddagi bog'liqlikni almashtirishni va "qayerda patch qilish" qoidasini o'rganamiz.

Bu darsda:

  • patch — kontekst menejeri va dekorator
  • "Qayerda ishlatilsa, o'sha yerda patch qil"
  • patch.object — obyekt atributini
  • patch.dict — lug'atni (masalan os.environ)
  • autospec — xavfsiz patch
  • MagicMock — sehrli metodlar
  • Bir necha patch birga
  • Amaliy: tarmoq chaqiruvli modulni test qilish

2. Nazariya — chuqur tushuntirish

2.1. patch — asosiy shakllar

patch mavjud obyektni test davomida almashtiradi va keyin avtomatik tiklaydi:

Kontekst menejeri sifatida:

python
from unittest.mock import patch

def test_xabar():
    with patch("app.haroratni_ol", return_value=25) as mock_h:
        assert app.xabar("Toshkent") == "Toshkent: 25°C"
        mock_h.assert_called_once_with("Toshkent")

Dekorator sifatida:

python
@patch("app.haroratni_ol", return_value=25)
def test_xabar(mock_h):            # mock argument sifatida keladi
    assert "25" in app.xabar("Toshkent")

Dekoratorda mock pastdan yuqoriga argument bo'lib keladi (bir necha @patch bo'lsa, tartibga e'tibor bering).

2.2. "Qayerda ishlatilsa, o'sha yerda patch qil"

Bu — mock ning eng muhim qoidasi. Patch obyekt ta'riflangan joyni emas, ishlatiladigan joyni o'zgartiradi:

python
# app.py
from vaqt import hozir_soat        # 'hozir_soat' endi app nomlar fazosida

def salom():
    return hozir_soat()
Patch Natija
patch("app.hozir_soat") Ishlaydi — app ishlatadigan nusxa
patch("vaqt.hozir_soat") Ishlamaydi — app allaqachon import qilgan

Sabab: from vaqt import hozir_soat app modulida yangi nom yaratadi. app.salom() shu nomni ishlatadi, vaqt.hozir_soat ni emas. Patch app.hozir_soat ni almashtirishi kerak.

Agar kod import vaqt qilib vaqt.hozir_soat() chaqirsa, u holda patch("vaqt.hozir_soat") to'g'ri — chunki ishlatilgan joy vaqt.hozir_soat.

2.3. patch.object — obyekt atributini

Butun yo'l o'rniga aniq obyekt va atribut nomi:

python
import app

with patch.object(app, "haroratni_ol", return_value=25):
    ...

# metodni patch qilish
with patch.object(TolovXizmati, "tolov_qil", return_value={"ok": True}):
    ...

patch.object — obyekt allaqachon qo'lda bo'lsa qulayroq va IDE tekshiruviga do'st.

2.4. patch.dict — lug'atni

Lug'atni (masalan muhit o'zgaruvchilari) vaqtincha o'zgartirish:

python
with patch.dict(os.environ, {"REJIM": "test", "DEBUG": "1"}):
    ...              # shu blokda o'zgargan
# blokdan keyin os.environ tiklanadi

patch.dict — monkeypatch.setenv 17.3-bob ning pytest siz varianti.

2.5. autospec — xavfsiz patch

Oddiy patch mock ni spec siz yaratadi — noto'g'ri metod nomi jim o'tadi. autospec=True mock ni asl obyekt imzosiga bog'laydi:

python
with patch("app.tolov_qil", autospec=True) as m:
    m.return_value = {"ok": True}
    # noto'g'ri argumentlar bilan chaqirilsa xato beradi

autospec=True — patch ni haqiqiy imzoga mos qiladi: xato argument soni yoki nomi ushlanadi.

2.6. MagicMock — sehrli metodlar

Mock sehrli (__len__, __getitem__, __iter__) metodlarni qo'llamaydi; MagicMock — qo'llaydi:

python
from unittest.mock import MagicMock

m = MagicMock()
m.__len__.return_value = 5
len(m)                             # 5
m[0]                               # ishlaydi

patch sukut bo'yicha MagicMock ishlatadi — shuning uchun ko'pincha alohida yaratish shart emas.

2.7. Bir necha patch

python
@patch("app.sms_yubor")
@patch("app.haroratni_ol")           # eng yaqin — birinchi argument
def test_x(mock_harorat, mock_sms):  # pastdan yuqoriga tartib
    ...

Dekoratorlar pastdan yuqoriga argumentga aylanadi: eng yaqin dekorator — birinchi parametr.

2.8. Qachon patch, qachon in'eksiya

Yondashuv Qachon
In'eksiya (o'tgan dars) Yangi kod — bog'liqlikni parametr qilib bering
patch Mavjud kod — in'eksiya qilinmagan bog'liqlik

Yangi kod yozayotganda bog'liqlikni in'eksiya qilish 13.5-bob mock qilishni osonlashtiradi va patch tuzoqlarini yo'q qiladi. patch — mavjud kodni o'zgartirmasdan test qilish uchun.


3. Tez ma'lumotnoma

python
from unittest.mock import patch, MagicMock

# kontekst menejeri
with patch("modul.funksiya", return_value=42) as m:
    ...
    m.assert_called_once()

# dekorator (mock pastdan argument bo'lib keladi)
@patch("modul.funksiya", return_value=42)
def test_x(mock_f): ...

patch.object(obyekt, "atribut", return_value=...)
patch.dict(os.environ, {"K": "V"})
patch("modul.funksiya", autospec=True)      # xavfsiz

Qoidalar

QAYERDA ISHLATILSA, o'sha yerda patch qil (ta'riflangan joy emas)
from X import Y → patch("mening_modul.Y")
import X; X.Y() → patch("X.Y")
patch avtomatik tiklaydi (with/dekorator)
autospec=True — imzoni tekshirish
os.environ — patch.dict
yangi kod — in'eksiya, mavjud kod — patch

4. Batafsil misollar

Misollarda pytest ni deterministik ishga tushiruvchi pytest_ishga yordamchisi ishlatiladi.

Misol 1 — patch: kontekst menejeri va dekorator

python
"""patch tashqi chaqiruvni almashtiradi; kontekst menejeri va dekorator shakllari; mock chaqiruvini tekshirish; patch avtomatik tiklaydi."""

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 = '''
def haroratni_ol(shahar):
    raise RuntimeError("haqiqiy tarmoq chaqiruvi — testda ishlamasligi kerak")

def xabar(shahar):
    t = haroratni_ol(shahar)
    holat = "issiq" if t > 25 else "sovuq"
    return f"{shahar}: {t} daraja ({holat})"
'''

TEST = '''
from unittest.mock import patch
import ob_havo

def test_kontekst_menejer():
    with patch("ob_havo.haroratni_ol", return_value=30) as mock_h:
        assert ob_havo.xabar("Toshkent") == "Toshkent: 30 daraja (issiq)"
        mock_h.assert_called_once_with("Toshkent")

def test_sovuq():
    with patch("ob_havo.haroratni_ol", return_value=10):
        assert ob_havo.xabar("Samarqand") == "Samarqand: 10 daraja (sovuq)"

@patch("ob_havo.haroratni_ol", return_value=26)
def test_dekorator(mock_h):
    assert "issiq" in ob_havo.xabar("Buxoro")
    assert mock_h.called

def test_patch_tiklandi():
    # oldingi testlardagi patch tiklangan — haqiqiy funksiya qaytdi
    import ob_havo
    try:
        ob_havo.haroratni_ol("X")
        tiklandi = False
    except RuntimeError:
        tiklandi = True
    assert tiklandi

def test_xato_holati():
    with patch("ob_havo.haroratni_ol", side_effect=TimeoutError("vaqt tugadi")):
        import pytest
        with pytest.raises(TimeoutError):
            ob_havo.xabar("Xiva")
'''


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

        print("=== 1. patch 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}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. patch bilan testlar ===
  test_ob_havo.py::test_kontekst_menejer PASSED
  test_ob_havo.py::test_sovuq PASSED
  test_ob_havo.py::test_dekorator PASSED
  test_ob_havo.py::test_patch_tiklandi PASSED
  test_ob_havo.py::test_xato_holati PASSED
  --- 5 passed ---
  chiqish kodi: 0

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

Misol 2 — Qayerda patch qilish kerak

python
"""patch 'qayerda ishlatilsa, o'sha yerda' qoidasi; from X import Y → app.Y ni patch qilish kerak, vaqt.Y ni emas; deterministik namoyish."""

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


# manba modul — 'joriy soat'ni beruvchi (demo uchun deterministik: 15 qaytaradi)
MANBA = '''
def hozir_soat():
    return 15        # demoda "joriy" soat — deterministik
'''

# 'from manba import hozir_soat' — nom 'app' fazosiga ko'chadi
APP = '''
from manba import hozir_soat

def salomlash():
    return "tong" if hozir_soat() < 12 else "kun"
'''

TEST = '''
from unittest.mock import patch
import app

def test_notogri_joy_ishlamaydi():
    # manba.hozir_soat ni patch qilamiz — LEKIN app allaqachon o'z nusxasini import qilgan
    with patch("manba.hozir_soat", return_value=9):
        # app.salomlash hali ham asl (15) qiymatni ishlatadi → "kun"
        assert app.salomlash() == "kun"      # patch ISHLAMADI

def test_togri_joy_ishlaydi():
    # app.hozir_soat ni patch qilamiz — app aynan shuni ishlatadi
    with patch("app.hozir_soat", return_value=9):
        assert app.salomlash() == "tong"     # patch ISHLADI

def test_patch_object_ham_togri():
    with patch.object(app, "hozir_soat", return_value=9):
        assert app.salomlash() == "tong"
'''


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

        print("=== 1. 'Qayerda patch qilish' testlari ===")
        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("  ⭐ manba.hozir_soat patch → app ta'sirlanmaydi (o'z nusxasi bor)")
        print("  ⭐ app.hozir_soat patch → ishlaydi (app aynan shuni chaqiradi)")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. 'Qayerda patch qilish' testlari ===
  test_joy.py::test_notogri_joy_ishlamaydi PASSED
  test_joy.py::test_togri_joy_ishlaydi PASSED
  test_joy.py::test_patch_object_ham_togri PASSED
  --- 3 passed ---
  chiqish kodi: 0
  ⭐ manba.hozir_soat patch → app ta'sirlanmaydi (o'z nusxasi bor)
  ⭐ app.hozir_soat patch → ishlaydi (app aynan shuni chaqiradi)

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

Misol 3 — patch.dict, autospec va MagicMock

python
"""patch.dict bilan os.environ; autospec noto'g'ri imzoni ushlaydi; MagicMock sehrli metodlar (__len__, __getitem__); patch sukut MagicMock ishlatadi."""

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 rejim():
    return os.environ.get("WISAR_REJIM", "ishlab_chiqarish")

def hisobla(a, b):
    return a + b
'''

TEST = '''
from unittest.mock import patch, MagicMock, Mock
import app

def test_patch_dict_env():
    with patch.dict("os.environ", {"WISAR_REJIM": "test"}):
        assert app.rejim() == "test"

def test_env_tiklandi():
    assert app.rejim() == "ishlab_chiqarish"

def test_autospec_notogri_imzo():
    with patch("app.hisobla", autospec=True) as m:
        m.return_value = 5
        app.hisobla(2, 3)                    # to'g'ri imzo
        m.assert_called_once_with(2, 3)
    # autospec bilan mock asl imzoga bog'langan

def test_magicmock_sehrli():
    m = MagicMock()
    m.__len__.return_value = 3
    m.__getitem__.return_value = "element"
    assert len(m) == 3
    assert m[0] == "element"

def test_oddiy_mock_sehrsiz():
    m = Mock()
    import pytest
    with pytest.raises(TypeError):
        len(m)                               # Mock __len__ ni qo'llamaydi
'''


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. patch.dict, autospec, MagicMock ===")
        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}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. patch.dict, autospec, MagicMock ===
  test_app.py::test_patch_dict_env PASSED
  test_app.py::test_env_tiklandi PASSED
  test_app.py::test_autospec_notogri_imzo PASSED
  test_app.py::test_magicmock_sehrli PASSED
  test_app.py::test_oddiy_mock_sehrsiz PASSED
  --- 5 passed ---
  chiqish kodi: 0

Nima ko'rsatdi: 2.4, 2.5, 2.6-bo'limlar.

Misol 4 — Amaliy: tarmoq chaqiruvli modulni test qilish

Valyuta kursini API dan olib, so'mga aylantiruvchi modulni test qilamiz. Modul urllib bilan haqiqiy so'rov yuboradi — bu in'eksiya qilinmagan, mavjud kod. patch bilan tarmoq chaqiruvini almashtirib, muvaffaqiyat, xato javob va tarmoq nosozligi holatlarini izolyatsiyada tekshiramiz — haqiqiy internetsiz, tez va barqaror.

python
"""mavjud kodni (urllib bilan) patch qilib test qilish: muvaffaqiyatli javob, xato status, tarmoq nosozligi (side_effect); chaqiruvni tekshirish; keshni tekshirish."""

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

KOD = '''
import json
import urllib.request

class ValyutaXatosi(Exception):
    pass

def kursni_ol(valyuta):
    """API dan valyuta kursini oladi (1 birlik necha so'm)."""
    try:
        javob = urllib.request.urlopen(f"https://api.example/rate?c={valyuta}", timeout=5)
    except OSError as e:
        raise ValyutaXatosi(f"tarmoq xatosi: {e}") from e
    malumot = json.loads(javob.read())
    if "rate" not in malumot:
        raise ValyutaXatosi(f"noma'lum valyuta: {valyuta}")
    return malumot["rate"]

def somga_aylantir(summa, valyuta):
    kurs = kursni_ol(valyuta)
    return round(summa * kurs, 2)
'''

TEST = '''
from unittest.mock import patch, MagicMock
import pytest
import valyuta

def soxta_javob(malumot: bytes):
    m = MagicMock()
    m.read.return_value = malumot
    return m

def test_muvaffaqiyatli_aylantirish():
    with patch("urllib.request.urlopen", return_value=soxta_javob(b'{"rate": 12650}')) as m:
        natija = valyuta.somga_aylantir(100, "USD")
        assert natija == 1265000.0
        assert m.called

def test_kurs_ol():
    with patch("urllib.request.urlopen", return_value=soxta_javob(b'{"rate": 13800}')):
        assert valyuta.kursni_ol("EUR") == 13800

def test_nomalum_valyuta():
    with patch("urllib.request.urlopen", return_value=soxta_javob(b'{"error": "not found"}')):
        with pytest.raises(valyuta.ValyutaXatosi, match="noma'lum valyuta"):
            valyuta.kursni_ol("XXX")

def test_tarmoq_nosozligi():
    with patch("urllib.request.urlopen", side_effect=OSError("ulanish yo'q")):
        with pytest.raises(valyuta.ValyutaXatosi, match="tarmoq xatosi"):
            valyuta.kursni_ol("USD")

def test_togri_url_chaqirilgan():
    with patch("urllib.request.urlopen", return_value=soxta_javob(b'{"rate": 100}')) as m:
        valyuta.kursni_ol("RUB")
        chaqiruv_url = m.call_args[0][0]
        assert "c=RUB" in chaqiruv_url

@patch("urllib.request.urlopen")
def test_dekorator_shakli(mock_urlopen):
    mock_urlopen.return_value = soxta_javob(b'{"rate": 12000}')
    assert valyuta.somga_aylantir(50, "USD") == 600000.0
'''


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 / "valyuta.py").write_text(KOD, encoding="utf-8")
        (r / "test_valyuta.py").write_text(TEST, encoding="utf-8")

        print("=== 1. Tarmoq chaqiruvli modul (patch bilan) ===")
        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("  ⭐ haqiqiy internetsiz: muvaffaqiyat, xato, tarmoq nosozligi tekshirildi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Tarmoq chaqiruvli modul (patch bilan) ===
  test_valyuta.py::test_muvaffaqiyatli_aylantirish PASSED
  test_valyuta.py::test_kurs_ol PASSED
  test_valyuta.py::test_nomalum_valyuta PASSED
  test_valyuta.py::test_tarmoq_nosozligi PASSED
  test_valyuta.py::test_togri_url_chaqirilgan PASSED
  test_valyuta.py::test_dekorator_shakli PASSED
  --- 6 passed ---

  chiqish kodi: 0
  ⭐ haqiqiy internetsiz: muvaffaqiyat, xato, tarmoq nosozligi tekshirildi

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


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

Noto'g'ri fikr To'g'risi
"Obyekt ta'riflangan joyda patch qilinadi" Ishlatiladigan joyda
"from X import Y da X.Y ni patch qilaman" mening_modul.Y ni
"Patch ni qo'lda tiklash kerak" with/dekorator avtomatik tiklaydi
"patch mock ni spec bilan yaratadi" Yo'q — autospec=True kerak
"Mock len() ni qo'llaydi" MagicMock qo'llaydi
"Dekoratorlar yuqoridan pastga argument" Pastdan yuqoriga
"os.environ ni to'g'ridan o'zgartirsam bo'ladi" patch.dict — avtomatik tiklaydi
"Patch — yagona usul" In'eksiya (yangi kod) — afzalroq

6. Keng tarqalgan xatolar va yechimlari

1. Noto'g'ri joyni patch qilish

python
# app.py: from api import olish
patch("api.olish", ...)                             # ❌ app ta'sirlanmaydi
patch("app.olish", ...)                             # ✅

2. Patch ni tiklamaslik (qo'lda)

python
app.olish = Mock()                                  # ❌ keyingi testga ta'sir
with patch("app.olish"): ...                        # ✅ avtomatik tiklaydi

3. autospec siz noto'g'ri imzo

python
with patch("app.tolov"):                            # ❌ noto'g'ri arg jim o'tadi
with patch("app.tolov", autospec=True):             # ✅ imzo tekshiriladi

4. Dekorator argument tartibi

python
@patch("app.a")
@patch("app.b")
def test(mock_a, mock_b): ...                       # ❌ tartib teskari
def test(mock_b, mock_a): ...                       # ✅ pastdan yuqoriga

5. os.environ ni to'g'ridan-to'g'ri

python
os.environ["X"] = "1"                               # ❌ tiklanmaydi
with patch.dict("os.environ", {"X": "1"}): ...      # ✅

6. len/[] uchun Mock

python
m = Mock(); len(m)                                  # ❌ TypeError
m = MagicMock(); m.__len__.return_value = 3         # ✅

7. Patch yo'lini noto'g'ri yozish

python
patch("olish", ...)                                 # ❌ to'liq yo'l kerak
patch("mening_paket.mening_modul.olish", ...)       # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 17.5-dars (o'tilgan): mock tushunchasi — patch mock yaratadi
  • 17.3-dars (o'tilgan): monkeypatch — pytest ning patch varianti
  • 17.9-dars: integratsion testlar — qachon patch, qachon haqiqiy
  • 20.15-dars: FastAPI — tashqi bog'liqliklarni patch qilish
  • 26.3–26.7-darslar: scraping va API — tarmoqni patch bilan test

8. Eng yaxshi amaliyotlar

  1. "Qayerda ishlatilsa, o'sha yerda patch qil" — ta'riflangan joyni emas.

  2. with/dekorator ishlating — patch avtomatik tiklanadi.

  3. autospec=True bilan imzoni tekshiring.

  4. os.environ va lug'atlar uchun patch.dict.

  5. Sehrli metodlar kerak bo'lsa MagicMock.

  6. Bir necha @patch da argument tartibiga (pastdan yuqoriga) e'tibor bering.

  7. Yangi kod yozayotganda bog'liqlikni in'eksiya qiling — patch dan afzal.

  8. Patch yo'lini to'liq va aniq yozing.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

# app.py: from vaqt import soat deb faraz qiling. Har patch ishlaydimi (ISHLAYDI/ISHLAMAYDI)?

python
1.  patch("app.soat", return_value=9)
2.  patch("vaqt.soat", return_value=9)      # app 'from vaqt import soat' qilgan
3.  patch.object(app, "soat", return_value=9)

Va quyidagilar:

python
from unittest.mock import Mock, MagicMock, patch
4.  m = MagicMock(); m.__len__.return_value = 2; print(len(m))
5.  m = Mock()
    try: len(m); print("ok")
    except TypeError: print("xato")
6.  with patch.dict("os.environ", {"X": "1"}):
        print(os.environ["X"])
7.  import os
    print("X" in os.environ)                # 6-blokdan keyin
8.  @patch("app.b")
    @patch("app.a")
    def test(m1, m2): print(m1._mock_name, m2._mock_name)   # qaysi qaysi
9.  with patch("app.f", return_value=5) as m:
        app.f(); print(m.call_count)
10. m = MagicMock(); print(bool(m))
11. m = Mock(return_value=None); print(m() is None)
12. with patch("app.f") as m:
        print(type(m).__name__)
Javoblar
  1. ISHLAYDI — app ishlatadigan nusxa
  2. ISHLAMAYDI — app o'z nusxasini import qilgan
  3. ISHLAYDI — app.soat ni almashtiradi
  4. 2
  5. xato — Mock __len__ ni qo'llamaydi
  6. 1
  7. False — patch.dict tikladi
  8. a b — eng yaqin dekorator birinchi argument (m1=app.a)
  9. 1
  10. True — MagicMock sukut rost
  11. True
  12. MagicMock — patch sukut MagicMock

Vazifa 2: Xatolarni tuzating

python
1.  # app.py: from requests import get
    def test_x():
        with patch("requests.get", return_value=soxta):
            assert app.olish() == kutilgan

2.  def test_env():
        os.environ["REJIM"] = "test"
        assert app.rejim() == "test"

3.  def test_len():
        m = Mock()
        m.__len__ = lambda: 5              # Mock sehrli metod
        assert len(m) == 5

4.  @patch("app.sms")
    @patch("app.email")
    def test_xabar(mock_sms, mock_email):
        app.xabar_yubor()

5.  def test_tolov():
        with patch("app.tolov_qil"):      # noto'g'ri imzo jim o'tadi
            app.buyurtma()
Javoblar
python
1.  # from requests import get → app.get ni patch qilish
    def test_x():
        with patch("app.get", return_value=soxta):
            assert app.olish() == kutilgan

2.  def test_env():
        with patch.dict("os.environ", {"REJIM": "test"}):
            assert app.rejim() == "test"

3.  def test_len():
        m = MagicMock()                   # MagicMock
        m.__len__.return_value = 5
        assert len(m) == 5

4.  @patch("app.sms")                     # pastdan yuqoriga argument
    @patch("app.email")
    def test_xabar(mock_email, mock_sms):
        app.xabar_yubor()

5.  def test_tolov():
        with patch("app.tolov_qil", autospec=True):   # imzo tekshiriladi
            app.buyurtma()

Vazifa 3: Email yuboruvchini test qilish

smtplib bilan email yuboradigan modulni:

  1. patch bilan smtplib.SMTP ni almashtiring
  2. Muvaffaqiyatli yuborish va SMTP xatosini test qiling
  3. To'g'ri manzil va mavzu bilan chaqirilganini tasdiqlang
  4. autospec bilan xavfsizroq qiling

Vazifa 4: Vaqtni patch qilish

datetime.now() ishlatadigan funksiyani:

  1. patch bilan aniq vaqt qaytaring (freeze)
  2. Turli sanalar uchun mantiqni test qiling (dam olish kuni, bayram)
  3. "Qayerda ishlatilsa" qoidasini to'g'ri qo'llang
  4. freezegun kutubxonasi bilan solishtiring (ixtiyoriy)

Vazifa 5: Fayl tizimini patch qilish

Fayl o'qiydigan funksiyani:

  1. patch bilan open ni mock_open yordamida almashtiring
  2. Fayl mazmuni, yo'q fayl, ruxsat xatosi holatlari
  3. pathlib.Path.read_text ni ham patch qiling
  4. Haqiqiy tmp_path 17.3-bob bilan solishtiring — qaysi biri afzal

Vazifa 6: Retry va timeout

Qayta urinuvchi tarmoq funksiyasini:

  1. side_effect ro'yxati bilan "ikki xato, keyin muvaffaqiyat"
  2. call_count bilan urinishlar sonini tasdiqlang
  3. Barcha urinish yiqilsa xato tarqalishini
  4. autospec bilan API imzosini himoya qiling

Vazifa 7: O'ylash

"Qayerda ishlatilsa, o'sha yerda patch qil" qoidasi mock ning eng ko'p adashtiigan jihati. Bu qoida Python import tizimining ishlash tarzidan kelib chiqadi. Nima uchun from modul import nom va import modul patch qilishda farq qiladi, va bu farq Python nomlar fazolari haqida nimani o'rgatadi?

Javob

Qisqa javob: from modul import nom nom ni joriy modulning nomlar fazosiga nusxalaydi — endi ikki joyda ikki nom bir obyektga ishora qiladi. patch bitta nomni almashtirsa, ikkinchisi eski obyektga ishora qilishda davom etadi. import modul esa modul nomini oladi va modul.nom har safar modul ichidan qidiriladi — shuning uchun modul.nom ni patch qilish ishlaydi. Bu Python'ning "nom — obyektga bog'lanish" tabiatini ochib beradi.

1. from modul import nom nima qiladi

python
# app.py
from vaqt import soat        # app.soat = vaqt.soat (nusxa bog'lanish)

Endi app.soat va vaqt.soat — ikki alohida nom, bir obyektga ishora qiladi. patch("vaqt.soat") faqat vaqt.soat ni almashtiradi; app.soat eski obyektga bog'langan qoladi.

2. import modul nima qiladi

python
# app.py
import vaqt

def salom():
    return vaqt.soat()       # har safar vaqt.soat qidiriladi

Bu yerda app faqat vaqt nomini oladi; vaqt.soat ishlatilganda qidiriladi. patch("vaqt.soat") ishlaydi, chunki qidirish patch qilingan joyga boradi.

3. Umumiy qoida

Import uslubi Patch qilinadigan joy
from X import Y (keyin Y()) mening_modul.Y
import X (keyin X.Y()) X.Y

4. Python nomlar fazosi darsi

Tushuncha Ma'nosi
Nom — bog'lanish O'zgaruvchi obyekt emas, unga ishora
from import — nusxa Yangi nom, bir obyekt
Modul — nomlar fazosi Har modulning o'z jadvali
Kechiktirilgan qidirish X.Y ishlatilganda qidiriladi

5. Amaliy xulosa

  1. Test yozishni osonlashtirish uchun import modul uslubi (patch moslashuvchan)
  2. Yoki bog'liqlikni in'eksiya qiling — patch tuzog'i umuman yo'q
  3. "Qayerda ishlatilsa" — nom qayerdan qidirilishiga bog'liq
  4. Bu Python nomlar fazolari va bog'lanishlarining tabiiy natijasi

6. Xulosa

  1. from import nomni nusxalaydi — patch ishlatilgan joyda
  2. import modul kechiktirilgan qidirish — patch manba joyda
  3. Bu Python "nom — bog'lanish" modelidan kelib chiqadi
  4. In'eksiya bu murakkablikni umuman yo'q qiladi

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda patch bilan mavjud koddagi bog'liqlikni almashtirishni o'rgandik.

Eng muhim uch fikr:

  1. patch mavjud obyektni vaqtincha almashtiradi va avtomatik tiklaydi. Kontekst menejeri (with patch(...)) yoki dekorator (@patch(...)) shaklida ishlaydi; dekoratorda mock pastdan yuqoriga argument bo'lib keladi. In'eksiya qilinmagan bog'liqlik (to'g'ridan-to'g'ri requests.get, datetime.now, open) uchun — in'eksiyadan farqli o'laroq — mavjud kodni o'zgartirmasdan test qilish yo'li.

  2. "Qayerda ishlatilsa, o'sha yerda patch qil." Bu — mock ning eng muhim va eng ko'p adashtiruvchi qoidasi. from modul import nom nomni joriy modul fazosiga nusxalaydi, shuning uchun patch("mening_modul.nom") kerak, patch("modul.nom") emas. import modul bo'lsa patch("modul.nom") to'g'ri. Bu Python nomlar fazosining tabiatidan kelib chiqadi.

  3. patch.object, patch.dict, autospec, MagicMock. patch.object — obyekt va atribut nomi bilan; patch.dict — lug'at (masalan os.environ) ni vaqtincha o'zgartirib avtomatik tiklaydi; autospec=True mock ni asl imzoga bog'lab noto'g'ri chaqiruvni ushlaydi; MagicMock sehrli metodlarni (__len__, __getitem__) qo'llaydi (patch sukut bo'yicha uni ishlatadi). Va eng yaxshi amaliyot: yangi kod yozayotganda bog'liqlikni in'eksiya qiling — bu patch tuzoqlarini umuman yo'q qiladi.

Keyingi darsda coverage ni o'rganamiz: testlar kodning qancha qismini qamrab olganini o'lchash, qaysi qatorlar test qilinmaganini topish va qamrov metrikasini to'g'ri (va noto'g'ri) talqin qilish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.6-dars: Mock — amaliyot — IlmHamroh