Asosiy kontentga o‘tish

Maqolalar

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

pytest-django: birinchi testni yozamiz

#python #django #testing

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 bor (unittest asosida), lekin Python olamida amalda standart — pytest: assert'lari oddiy, chiqishi o'qishli, fixture tizimi kuchli. Django bilan do'stlashtirish 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#

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 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

# kecha va ertaga — testlarda qayta-qayta kerak bo'ladi
KECHA = timezone.localdate() - timedelta(days=1)
ERTAGA = timezone.localdate() + 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

Ishga tushiramiz:

uv run pytest
# ...
# 3 passed in 0.42s

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: uchta shartga uchta alohida test, bitta katta test emas.

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:

FAILED test_bajarilgan_vazifa_kechikkan_hisoblanmaydi
    assert not task.is_overdue
E   assert not True

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.