Maqolalar 8 daqiqa 408 ko‘rish
pytest-django: birinchi testni yozamiz
Kod ishlayaptimi — brauzerda bosib ko'rish bilan tekshirib yurgan bo'lsangiz, bu maqola siz uchun. pytest-django'ni sozlaymiz, birinchi testni yozamiz va «o'zgartirishdan qo'rqmaslik» degan tuyg'u qayerdan kelishini ko'ramiz.
Mundarija
Kod yozdingiz, brauzerda ochib bosib ko'rdingiz — ishlayapti. Bir haftadan keyin boshqa joyini o'zgartirdingiz va bilmagan holda avvalgi ishlagan joyni buzdingiz. Buni foydalanuvchi topib beradi. Tanish holatmi? Men ham shu yo'ldan o'tganman va testlarga jiddiy qarashim aynan ToDo loyihasidan boshlangan — uni ataylab "test intizomini o'rganish" maqsadida qurganman.
Testning ma'nosini bitta gap bilan aytish mumkin: bosib ko'rishni kod qilib yozib qo'yish. Bir marta yozasiz, keyin har o'zgarishdan so'ng kompyuter siz uchun ming marta bosib ko'radi.
Nega aynan pytest#
Django'ning o'z test tizimi ham bor, u unittest asosida ishlaydi. Lekin
Python dunyosida amalda standartga aylangan vosita — pytest. Unda assert
yozish oddiy, xato chiqqanda terminaldagi xabar tushunarli, fixture tizimi
esa kuchli. Uni Django bilan bog'lash uchun bitta plagin yetadi:
uv add --dev pytest pytest-django
Sozlama ham bitta fayl — pytest'ga Django sozlamalarini qayerdan olishni aytamiz:
# pytest.ini (loyiha ildizida)
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
python_files = test_*.py
Loyiha va birinchi test#
Misol uchun kichik loyiha yaratamiz (Django loyihangiz allaqachon bo'lsa,
faqat apps/todos qismini qo'shing):
uv add django
uv run django-admin startproject config .
mkdir -p apps/todos/tests
touch apps/__init__.py apps/todos/__init__.py apps/todos/tests/__init__.py
# config/settings.py
INSTALLED_APPS = [
# ...
"apps.todos",
]
INSTALLED_APPS dagi qatorni unutmang: busiz pytest testni ishga tushirishga
ham ulgurmaydi — fayllarni yig'ish bosqichidayoq
RuntimeError: Model class apps.todos.models.Task doesn't declare an explicit
app_label and isn't in an application in INSTALLED_APPS xatosi chiqadi.
Misolimiz — ToDo ilovasining soddalashtirilgan modeli:
# apps/todos/models.py
from django.db import models
from django.utils import timezone
class Task(models.Model):
title = models.CharField(max_length=200)
is_done = models.BooleanField(default=False)
due_date = models.DateField(null=True, blank=True)
@property
def is_overdue(self) -> bool:
# muddat o'tgan va hali bajarilmagan — kechikkan
if self.due_date is None or self.is_done:
return False
return self.due_date < timezone.localdate()
is_overdue — test yozishni boshlash uchun ideal nomzod: kichkina, lekin
ichida uchta shart va bitta nozik chegara yashiringan mantiq. Test fayli model bilan yonma-yon
turadi:
apps/todos/
├── models.py
└── tests/
├── __init__.py
└── test_models.py ← birinchi testimiz shu yerda
# apps/todos/tests/test_models.py
from datetime import timedelta
import pytest
from django.utils import timezone
from apps.todos.models import Task
# bugun va kecha — testlarda qayta-qayta kerak bo'ladi
BUGUN = timezone.localdate()
KECHA = BUGUN - timedelta(days=1)
@pytest.mark.django_db
def test_muddati_otgan_vazifa_kechikkan():
task = Task.objects.create(title="Hisobot", due_date=KECHA)
assert task.is_overdue
@pytest.mark.django_db
def test_bajarilgan_vazifa_kechikkan_hisoblanmaydi():
task = Task.objects.create(title="Hisobot", due_date=KECHA, is_done=True)
assert not task.is_overdue
@pytest.mark.django_db
def test_muddatsiz_vazifa_kechikmaydi():
task = Task.objects.create(title="Kitob o'qish")
assert not task.is_overdue
@pytest.mark.django_db
def test_muddati_bugun_bolgan_vazifa_hali_kechikmagan():
# chegaraviy holat: muddat bugun tugaydi — kun hali tugamagan
task = Task.objects.create(title="Hisobot", due_date=BUGUN)
assert not task.is_overdue
Ishga tushiramiz:
uv run pytest
# ...
# 4 passed in 0.05s
Uch nuqtaga e'tibor qarataman. Birinchisi — @pytest.mark.django_db
belgisi: usiz test bazaga tegolmaydi. Bu ataylab qilingan, chunki bazasiz
test yashin tezligida yuradi va pytest sizni "bazani faqat kerak joyda
ishlat" deb tarbiyalaydi. Ikkinchisi — test nomlari o'zbekcha va gap kabi
o'qiladi: test yiqilganda nomining o'zi nima buzilganini aytib turadi.
Uchinchisi — har test bitta narsani tekshiradi: to'rt holatga to'rtta
alohida test, bitta katta test emas.
To'rtinchi test — chegaraviy holat, va u eng qimmatlisi. < o'rniga
tasodifan <= yozilsa, muddati bugun tugaydigan vazifa ertalabdanoq
"kechikkan" bo'lib ko'rinadi. Muddati o'tgan, bajarilgan va muddatsiz
holatlar uchun yozilgan uchta test bu xatoni sezmaydi — sinovda ular
<= bilan ham yashil qoldi. Faqat due_date=BUGUN testi qizarib, xatoni
ushladi.
Test bazasi haqida xotirjam bo'ling#
Boshlovchilarni ko'p tashvishlantiradigan savol: "testlar mening bazamni
buzib qo'ymaydimi?" Yo'q. pytest-django har test seansi uchun alohida,
vaqtinchalik baza yaratadi (nomi odatda test_ bilan boshlanadi), ishini
o'sha yerda qiladi va oxirida o'chirib tashlaydi. Har test esa tranzaksiya
ichida yuradi va tugagach orqaga qaytariladi — ya'ni testlar bir-birining
ma'lumotini ham ko'rmaydi. Siz Task.objects.create bilan nima yaratmang,
haqiqiy bazangizga bitta yozuv ham tushmaydi.
Yiqilgan test — bu yaxshi test#
Endi eng muhim mashqni qilamiz: mantiqni ataylab buzamiz. is_overduedagi
self.is_done shartini olib tashlab, testlarni qayta ishga tushiring:
________________ test_bajarilgan_vazifa_kechikkan_hisoblanmaydi ________________
@pytest.mark.django_db
def test_bajarilgan_vazifa_kechikkan_hisoblanmaydi():
task = Task.objects.create(title="Hisobot", due_date=KECHA, is_done=True)
> assert not task.is_overdue
E assert not True
E + where True = <Task: Task object (1)>.is_overdue
apps/todos/tests/test_models.py:22: AssertionError
=========================== short test summary info ============================
FAILED apps/todos/tests/test_models.py::test_bajarilgan_vazifa_kechikkan_hisoblanmaydi
========================= 1 failed, 3 passed in 0.06s ==========================
pytest qaysi test, qaysi qatorda va qaysi qiymat sabab yiqilganini aniq
ko'rsatadi: > belgisi yiqilgan qatorga ishora qiladi, where True = ...
esa is_overdue aynan True qaytarganini aytadi.
Mana shu qizil satr — testlarning butun qiymati. Siz xatoni foydalanuvchidan emas, terminaldan, o'zgarish qilgan daqiqangizda eshitdingiz. Endi shartni joyiga qaytaring — yashil. Bu tsiklni his qilgan odam testsiz loyihaga qaytishi qiyin.
Testni qachon yozish kerak?
Ideal javob "koddan oldin" (TDD), lekin halol boshlanish — "kod bilan
birga": yangi mantiq yozdingizmi, o'sha o'tirishning o'zida 2-3 test
ham yozing. Eng foydali joylar — shartli mantiq (is_overdue kabi),
hisob-kitoblar va chegaraviy holatlar. Getter-setter'larga test yozib
vaqt ketkazmang.
Yakun#
pytest-django'ni ishga tushirish ikki qadam edi: plagin va pytest.ini.
Undan keyingisi — odat masalasi: har mantiqqa uch-to'rt satr test, nomlari
gap kabi, har biri bitta narsani tekshiradi. Baza haqida qayg'urmaysiz —
u vaqtinchalik va sizniki emas.
Keyingi qismda ko'lamni kengaytiramiz: unit va integration testlar farqi — "kichik va tez" bilan "katta va ishonchli" o'rtasidagi muvozanatni qanday tanlash.