Maqolalar 9 daqiqa 898 ko‘rish
Unit va integration: bitta funksiyaga ikki xil test
Hamma testlarni bir xil yozish — sekin va mo'rt test to'plamiga olib boradi. Unit test kichik va chaqqon, integration test katta va ishonchli. Bitta misol ustida ikkalasini yozib, farqini va qachon qaysi biri kerakligini aniqlaymiz.
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.shortcuts import render
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})
View ishlashi uchun unga manzil va shablon kerak — integration test aynan shu bo'laklarning ham joyida ekanini tekshiradi:
# apps/todos/urls.py
from django.urls import path
from apps.todos.views import dashboard
urlpatterns = [
path("", dashboard, name="dashboard"),
]
# config/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("todos/", include("apps.todos.urls")),
]
{# apps/todos/templates/todos/dashboard.html #}
<h1>Vazifalar</h1>
{% for name, tasks in groups.items %}
<h2>{{ name }}</h2>
<ul>
{% for task in tasks %}<li>{{ task.title }}</li>{% empty %}<li>—</li>{% endfor %}
</ul>
{% endfor %}
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. Buni o'zim ham ko'rdim: URL,
render importi va shablon bo'lmaganida bu test ketma-ket 404,
NameError va TemplateDoesNotExist bilan yiqildi — group_tasks ning
unit testlari esa uchala holatda ham yashil edi.
Narxi ham shunga yarasha. O'lchab ko'rdim: group_tasks chaqiruvi taxminan
0,3 mikrosoniya oladi, client.get("/todos/") esa 0,2 millisoniya atrofida
— yuzlab barobar ko'p. Bunga har bazali test uchun test bazasini tayyorlash
vaqti ham qo'shiladi. Aniq raqamlar kompyuterga bog'liq, lekin nisbat shu.
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.