Модуль 32

Тестування Django-додатків

Ось урок на тему "Тестування Django-додатків", створений у стилі CS50: енергійно, зрозуміло та з фокусом на те, навіщо ми це робимо.


🎓 CS50: Тестування Django-додатків. Від хаосу до стабільності

Вітаю, друзі! Це CS50 (українська версія), і сьогодні ми поговоримо про те, що відрізняє аматора від професіонала. Про те, що дозволяє спати спокійно вночі, навіть якщо ваш код працює на серверах найбільшого банку країни.

Ми поговоримо про Тестування.


1. 🔥 Вступ: Чому це питання життя і смерті вашого проєкту?

Уявіть ситуацію. Ви будуєте хмарочос. Ви кладете цеглу за цеглою, поверхи ростуть. Але ви ніколи не перевіряєте міцність бетону, поки не збудуєте весь будинок. Страшно заходити в такий будинок, правда?

У програмуванні те саме. Ви пишете нову функцію — наприклад, систему знижок у вашому інтернет-магазині. Ви натискаєте "Зберегти". Знижки працюють! Ура! Але раптом... перестала працювати авторизація користувачів. Чому? Бо десь у глибині коду ви випадково зачепили стару логіку.

Це називається регресія — коли нове ламає старе.

Питання до вас: Скільки часу ви готові витрачати на те, щоб вручну клацати кожну кнопку на вашому сайті після кожної найменшої зміни в коді? Годину? День?

Якщо ваша відповідь "ніскільки", то вам потрібні автоматичні тести.

Аналогія: Тести — це як страхові троси для альпініста. Ви можете лізти без них, якщо ви дуже сміливі (або божевільні). Але коли ви зірветесь (а ви зірветесь, бо всі помиляються), трос врятує вам життя. Тест врятує ваш проєкт від краху в продакшені.


2. 🧠 Теоретична база (Що там "під капотом"?)

Давайте без нудних академічних визначень. Тест — це просто шматок коду, який перевіряє інший шматок коду. Все.

У Django є вбудований модуль для тестування, який базується на стандартній бібліотеці Python unittest.

Як це працює магічно ("під капотом"):

Коли ви запускаєте команду перевірки тестів, Django робить дивовижну річ: 1. Створює окрему, чисту базу даних (щоб ви випадково не видалили реальних юзерів!). 2. Застосовує всі міграції (будує структуру таблиць). 3. Проганяє ваші сценарії (тести). 4. Видаляє цю тестову базу даних і каже вам результат: ✅ OK або ❌ FAILED.

Що треба знати залізно:

  • Unit-тести (модульні): перевіряють найменшу деталь (наприклад, чи правильно функція додає 2+2).
  • Integration-тести (інтеграційні): перевіряють, як деталі працюють разом (наприклад, чи створюється замовлення в базі, коли юзер натискає "Купити").
  • TestCase: це головний клас у Django, від якого ми будемо успадковувати наші тести.

Інтуїтивно зрозумійте: Тест слідує схемі "AAA": 1. Arrange (Підготуй): створи дані. 2. Act (Дій): виклич функцію, яку перевіряєш. 3. Assert (Стверджуй): перевір, чи результат відповідає очікуванням.


3. 🧪 Приклади: Від "Hello World" до реальності

Припустимо, у нас є простий додаток blog.

Рівень 1: Найпростіша математика

Відкрийте файл tests.py у вашому додатку. Якщо його немає — створіть.

from django.test import TestCase

class SimpleMathTest(TestCase):
    def test_addition(self):
        """Перевіряємо, чи працює математика у цьому всесвіті"""
        result = 1 + 1
        self.assertEqual(result, 2)

Питання до вас: Що станеться, якщо я напишу result = 1 + 2? Відповідь: Тест впаде з помилкою AssertionError: 3 != 2. І це чудово! Червоний колір у консолі — ваш друг. Він каже правду.


Рівень 2: Тестуємо Модель (Logic)

У нас є модель Post:

# models.py
class Post(models.Model):
    title = models.CharField(max_length=200)
    is_published = models.BooleanField(default=False)

    def publish(self):
        self.is_published = True
        self.save()

Давайте перевіримо метод publish.

# tests.py
from django.test import TestCase
from .models import Post

class PostModelTest(TestCase):
    def test_publish_method(self):
        # 1. Arrange: створюємо пост (він ще не в реальній БД, а в тестовій!)
        post = Post.objects.create(title="Мій перший пост")

        # Перевірка "до": має бути False
        self.assertFalse(post.is_published)

        # 2. Act: викликаємо метод
        post.publish()

        # 3. Assert: перевіряємо результат
        self.assertTrue(post.is_published)

Чому це важливо? Якщо завтра ваш колега випадково змінить метод publish і видалить self.save(), цей тест миттєво закричить про помилку. Ви захищені.


Рівень 3: Тестуємо View (Чи відкривається сторінка?)

Користувачі не викликають методи моделей, вони ходять по URL. Перевіримо це.

from django.urls import reverse

class HomePageTest(TestCase):
    def test_home_page_status_code(self):
        # Використовуємо тестовий клієнт (як фейковий браузер)
        response = self.client.get('/') 

        # Ми очікуємо статус 200 (OK)
        self.assertEqual(response.status_code, 200)

    def test_home_page_content(self):
        response = self.client.get('/')
        # Перевіряємо, чи є на сторінці певний текст
        self.assertContains(response, "Ласкаво просимо до блогу")

Як це працює: self.client — це манекен, який "прикидається" браузером. Він робить запит, отримує відповідь, але не малює картинки, а просто тримає дані в пам'яті.


4. 🛠 Практична частина

Час брати клавіатуру в руки! Запустіть термінал.

Як запускати тести:

python manage.py test

Ваші завдання:

  1. 🔹 Повторення: Створіть файл tests.py у своєму проєкті (або використайте існуючий) і скопіюйте туди SimpleMathTest. Запустіть. Переконайтеся, що бачите OK.
  2. 🔹 Саботаж: У SimpleMathTest змініть очікування на self.assertEqual(1 + 1, 5). Запустіть. Подивіться, як виглядає помилка (Fail). Поверніть як було.
  3. 🔹 Реальна задача (Models): Якщо у вас є будь-яка модель (наприклад Product з полем price), напишіть тест, який перевіряє, що при створенні об'єкта він дійсно зберігається в базі. Підказка: перевірте Product.objects.count().
  4. 🔹 Реальна задача (Views): Напишіть тест для сторінки, якої не існує (наприклад, /super-secret-page/). Перевірте, що статус відповіді дорівнює 404.
  5. 🔹 Виправлення помилки (Кейс):
    • Уявіть функцію: def discount(price): return price * 0.5 (знижка 50%).
    • Напишіть тест, який очікує, що discount(100) поверне 50.
    • Тепер змініть умови: знижка має бути 20%. Змініть тест, запустіть (він впаде), а потім виправте функцію, щоб тест пройшов. Це і є TDD (Test Driven Development) у мініатюрі!
  6. 🔹 А що, якщо... Напишіть тест, який перевіряє створення користувача без пароля. Django має викинути помилку. Використайте self.assertRaises(...).

5. 💡 Мислення як у розробника

Як відрізнити новачка від сеньйора за тестами?

Помилки новачка: * ❌ Тестування Django: Не треба тестувати, чи працює models.CharField. Розробники Django це вже протестували. Тестуйте свою логіку. * ❌ Гігантські тести: Один тест перевіряє реєстрацію, створення товару, оплату і відправку пошти одночасно. Якщо він впаде — ви не зрозумієте де саме. Правило: Один тест — одна конкретна перевірка. * ❌ Залежність: Тест №2 працює тільки якщо пройшов Тест №1. Ні! Кожен тест має бути незалежним. Django стирає базу після кожного тесту саме для цього.

Думки професіонала:

"Я лінивий. Мені ліньки перевіряти це руками. Я напишу тест один раз, і нехай комп'ютер перевіряє це за мене тисячу разів."

"Якщо я знайшов баг, я спочатку пишу тест, який відтворює цей баг (він має впасти), а тільки потім фікшу баг (тест стає зеленим). Так цей баг ніколи не повернеться."


6. 🧩 Підсумок

Сьогодні ми не просто вивчили команду python manage.py test. Ми змінили філософію розробки.

Тепер ви вмієте: 1. Створювати Unit-тести на базі TestCase. 2. Перевіряти роботу моделей та Views. 3. Розуміти різницю між assertEqual, assertTrue, assertContains. 4. А головне — ви знаєте, як захистити свій код від майбутніх помилок.

Навіщо це все було? Щоб ваш код був як швейцарський годинник, а не як картковий будиночок.

🔍 Тизер наступного уроку: Окей, тести у нас є. Але як зробити так, щоб вони запускалися самі, коли ви завантажуєте код на GitHub? Наступного разу поговоримо про CI/CD (Continuous Integration) — роботів, які працюють за нас!

А поки що — пишіть код, ламайте його тестами і лагодьте знову! Це і є програмування. 🚀