Asosiy kontentga o‘tish

Maqolalar

Darslik · 2026-08-16 · ~7 daqiqa o‘qiladi · boshlang'ich

Unit va integration: bitta funksiyaga ikki xil test

#python #django #testing

Mundarija

Avvalgi qismda birinchi testlarni yozdik va ular hammasi bir ko'rinishda edi. Loyiha o'sgani sari esa savol tug'iladi: view'ni testlaymanmi, funksiyanimi, ikkalasinimi? Har so'rovga bazaga yozish shartmi? Bu savollarning ortida ikki tushuncha turadi — unit va integration testlar. Farqini bilib olsangiz, qaysi joyga qanday test yozishni o'ylab o'tirmaydigan bo'lasiz.

Ta'rifni ham misolning o'zida beraman. ToDo ilovamizga kichik funksiya qo'shamiz: foydalanuvchi vazifalarini "bugungi, kechikkan, kelgusi" deb uch guruhga ajratish.

Mantiqni ajratib yozamiz — testlanadigan qilib#

Birinchi qaror kodda bo'ladi, testda emas. Guruhlash mantiqini view ichiga emas, alohida sof funksiyaga yozamiz:

# apps/todos/services.py
from datetime import date

from apps.todos.models import Task


def group_tasks(tasks: list[Task], today: date) -> dict[str, list[Task]]:
    """Vazifalarni sanaga qarab uch guruhga ajratadi.

    E'tibor bering: bazaga tegmaydi, bugungi sanani ham o'zi olmaydi —
    hammasi tashqaridan keladi. Aynan shu ikkala qaror uni unit test
    uchun ideal qiladi.
    """
    groups = {"bugun": [], "kechikkan": [], "kelgusi": []}
    for task in tasks:
        if task.due_date is None or task.is_done:
            continue
        if task.due_date < today:
            groups["kechikkan"].append(task)
        elif task.due_date == today:
            groups["bugun"].append(task)
        else:
            groups["kelgusi"].append(task)
    return groups
# apps/todos/views.py
from django.utils import timezone

from apps.todos.models import Task
from apps.todos.services import group_tasks


def dashboard(request):
    tasks = Task.objects.filter(is_done=False)
    groups = group_tasks(list(tasks), timezone.localdate())
    return render(request, "todos/dashboard.html", {"groups": groups})

Unit test: kichik, chaqqon, aniq#

Unit test bitta bo'lakni yakka holda tekshiradi. group_tasks bazaga tegmagani uchun testda ham bazaga hojat yo'q — Task obyektlarini saqlamasdan, xotirada yasab beramiz:

# apps/todos/tests/test_services.py
from datetime import date

from apps.todos.models import Task
from apps.todos.services import group_tasks

BUGUN = date(2026, 8, 16)


def make_task(due):
    # saqlanmagan obyekt — bazasiz, shuning uchun django_db belgisi ham kerak emas
    return Task(title="ish", due_date=due)


def test_uch_guruh_togri_ajratiladi():
    tasks = [
        make_task(date(2026, 8, 15)),  # kecha  → kechikkan
        make_task(BUGUN),  # bugun  → bugun
        make_task(date(2026, 8, 20)),  # keyin  → kelgusi
    ]
    groups = group_tasks(tasks, today=BUGUN)
    assert [t.due_date for t in groups["kechikkan"]] == [date(2026, 8, 15)]
    assert len(groups["bugun"]) == 1
    assert len(groups["kelgusi"]) == 1


def test_bajarilgan_va_muddatsizlar_chiqmaydi():
    tasks = [Task(title="tayyor", due_date=BUGUN, is_done=True), Task(title="muddatsiz")]
    groups = group_tasks(tasks, today=BUGUN)
    assert groups == {"bugun": [], "kechikkan": [], "kelgusi": []}

Bu testlar millisekundlarda yuradi — yuztasi ham bir soniyaga sig'adi. Sababi oddiy: baza yo'q, Django'ning o'zi ham deyarli ishtirok etmayapti. todayni parametr qilganimiz ham shu yerda o'zini oqladi: sanani xohlagancha "burab", chegaraviy holatlarni bemalol tekshiramiz.

Integration test: bo'laklar birga ishlaydimi?#

Unit testlar mantiq to'g'riligini aytdi, lekin bitta savolga javob berolmaydi: view, URL, baza va shu funksiya birgalikda to'g'ri ulanganmi? Buni integration test tekshiradi — u haqiqiy so'rov yuboradi va butun zanjirni bosib o'tadi:

# apps/todos/tests/test_views.py
from datetime import timedelta

import pytest
from django.utils import timezone

from apps.todos.models import Task


@pytest.mark.django_db
def test_dashboard_kechikkanlarni_korsatadi(client):
    kecha = timezone.localdate() - timedelta(days=1)
    Task.objects.create(title="Hisobot topshirish", due_date=kecha)

    response = client.get("/todos/")

    assert response.status_code == 200
    assert "Hisobot topshirish" in response.content.decode()
    assert response.context["groups"]["kechikkan"][0].title == "Hisobot topshirish"

Bitta test — lekin ortida katta zanjir: URL marshruti to'g'ri, view ishladi, baza so'rovi ketdi, group_tasks chaqirildi, shablon render bo'ldi. Shu zanjirning istalgan joyi uzilsa, test yiqiladi. Narxi ham shunga yarasha: bunday test unit testdan o'nlab barobar sekin.

Muvozanat: piramida#

Endi asosiy savolga javob berish mumkin — qaysi biridan qancha yozamiz? Klassik javob piramida ko'rinishida:

        /  E2E  \          juda oz: brauzer orqali butun tizim
       / integr. \         o'rtacha: har muhim oqimga bittadan
      /   unit    \        ko'p: har mantiq bo'lagiga bir nechta

Amaliy qoidam shunday: mantiqning har bir sharti unit testda, har muhim foydalanuvchi oqimi bitta integration testda. Yuqoridagi misolda uchta shartli guruhlash — unit'da to'liq qamraldi, "dashboard ochiladi va kechikkanlarni ko'rsatadi" oqimi — bitta integration'da. Ikkalasini almashtirib bo'lmaydi: hamma narsani integration qilib yozsangiz, test to'plami sekinlashib boradi va bir kuni "testlarni keyin yuritamiz" degan gap chiqadi; hammasi unit bo'lsa, mantiq to'g'ri-yu ulanish uzuq bo'lgan xatolar (noto'g'ri URL, unutilgan import) sizdan qochib qoladi.

Tashqi xizmatlar-chi?

So'rov ichida Telegram API, to'lov tizimi yoki boshqa tashqi xizmat bo'lsa, testda unga chiqilmaydi — o'sha chaqiruv mock bilan almashtiriladi. Buning jonli misolini FCM push maqolasida ko'rgansiz: Firebase'ga chiqmasdan, javobini yasab berganmiz. Integration testning chegarasi — sizning tizimingiz; tashqi dunyo unga kirmaydi.

Yakun#

Farq eslab qolish uchun bitta jumlaga sig'adi: unit test "shu bo'lak to'g'ri o'ylaydimi" deb so'raydi, integration test — "bo'laklar bir-biriga to'g'ri ulanganmi". Birinchisi ko'p va tez, ikkinchisi kam va salmoqli. Va bunga erishishning kaliti testda emas, kodda ekanini ko'rdik: mantiq alohida sof funksiyada tursa, ikkala turdagi test ham oson yoziladi.

Keyingi qismda xuddi shu yondashuvni API'ga olib o'tamiz: DRF'ni APIClient bilan testlash — endpoint'lar, statuslar va JSON javoblar.