Програмування

Мій перший досвід написання тестів на Python: посібник для тих, хто сумнівається

Довгий час написання тестів здавалося мені чимось для «суворих ентерпрайз-розробників». Але це стає справді важливим під час програмування за допомогою ШІ, особливо за доволі масштабних змін, коли доводиться тестувати буквально все, щоб переконатися, що нічого не зламалося.

У мене був робочий пет-проєкт — утиліта на Python для підписування зображень. Під час спроби її рефакторингу зламалося дуже багато, усі функції довелося тестувати заново, і недарма. Тож, щоб знати, де й що відвалилося, добре було б мати тести.

Страх №1: «Доведеться переписувати частину коду проєкту»

Мій перший і найголовніший страх: щоб упровадити тести, мені доведеться якось змінювати робочий код під якусь «архітектуру, придатну для тестування».

Реальність: Нічого змінювати не довелося! Я просто створив поруч папку tests і поклав туди порожній файлик init.py (щоб Python зрозумів, що це пакет), а також свої перші тестові скрипти: test_db.py і test_main.py.

Усе, що мені знадобилося встановити, — це пару пакетів:

pip install pytest pytest-cov

pytest сам знаходить усі файли, що починаються на test_, і запускає їх.

Моки (Mock) і фікстури (Fixture): як тестувати інтерфейс без самого інтерфейсу

Головна складність мого проєкту — графічний інтерфейс (GUI) на Tkinter. Як тестувати натискання кнопок, якщо вікно зависає й чекає дій користувача? А якщо вискакує віконце з помилкою messagebox.showerror — тест зупиняється?

Виявилося, у Python є геніальна річ — unittest.mock. Вона дозволяє створювати «шпигунів» і підміняти справжні функції їхньою імітацією.

Наприклад, щоб тести не блокувалися спливними вікнами, я просто «замокав» їх:

from unittest.mock import patch

with patch('main.messagebox.showinfo'):
    # Тепер, якщо програма викличе showinfo, 
    # вікно не з’явиться, але тест зафіксує, що виклик був!
    app.do_something()

А щоб щоразу не створювати вікно Tkinter з нуля (це спричиняє помилки на Windows, якщо робити так десятки разів за секунду), я використовував фікстури. Фікстура — це ніби «підготовча база» для тесту. Я створив одне вікно Tkinter, приховав його (root.withdraw()), і всі тести спокійно працювали з цим прихованим вікном у фоновому режимі.

Тестування роботи з файлами: tmp_path нас урятує

Моя програма перейменовує зображення та зберігає файли requirements.txt. Я дуже боявся, що тести почнуть створювати сміття на моєму жорсткому диску або, чого доброго, видалять справжні фотографії.

Тут pytest дарує нам чарівну вбудовану фікстуру tmp_path. Вона автоматично створює тимчасову папку для кожного тесту й сама видаляє її після завершення!

У результаті тест перейменування файлу виглядає так: ми створюємо віртуальний файл screen.png у тимчасовій папці, передаємо його програмі, викликаємо функцію main.py#rename_file() і перевіряємо:

  1. Чи зник старий файл?
  2. Чи з’явився новий файл?
  3. Чи оновилося ім’я в базі даних?

Усе це робиться найпростішими assert:

assert (tmp_path / "new_img1.png").exists() # Новий файл має з’явитися!

Як протестувати те, чого немає на екрані? (Крадемо функції)

Найцікавіше було, коли я захотів протестувати функцію масового пошуку та заміни тексту. Вікно пошуку з’являється інтерактивно, усередині нього створюється кнопка, а до кнопки прив’язана внутрішня функція main.py#perform_replace().

За допомогою моків я зміг перехопити сам процес створення кнопки інтерфейсу, «витягнути» з неї цю функцію main.py#perform_replace і запустити її напряму просто в тесті, підсунувши їй фейкові значення в поля введення! Це викликало справжній захват — я перевірив роботу інтерфейсу, жодного разу не відмалювавши його на екрані.

«Покриття коду» (Coverage)

Коли всі тести позеленіли, постало питання: а чи все я протестував? Тут на сцену виходить плагін pytest-cov.

Я запустив команду:

pytest --cov=main --cov=db --cov-report=html tests/

Вона згенерувала HTML-сторінку, у якій мій робочий код розфарбований у кольори. Зелений рядок — тест сюди дійшов. Червоний рядок — тут тест не бував.

Спочатку я захотів 100% покриття. Але швидко зрозумів, що червоними залишаються дуже специфічні ділянки коду: наприклад, except OSError (коли файл фізично заблокований іншим процесом). Імітувати такі системні збої в тестах можна, але для невеликого проєкту це зайве. Покриття у 80–90% дало мені цілковиту впевненість у тому, що основа програми надійна, як швейцарський годинник.

Агенти

Звісно, усе було зовсім не так) Було швидше й простіше — інакше, без ШІ та агента, я витратив би багато днів.

Звісно, агент може написати тести, навіть якщо ви взагалі нічого не розумітимете й не перевірятимете за ним. Але я використовував його для швидкого старту: просив розповісти про тести, він показував приклади й пояснював концепції, я розпитував про різні технічні моменти — як краще робити та звідки беруться дані про покриття коду тестами. Усе це виявилося не лише цікавим, а й корисним для кращого розуміння, що тестувати і як. Звісно, я не можу сказати, що колись писатиму їх вручну: усе-таки це повільна рутина. А агенти зрештою для цього й потрібні — робити те, що з їхньою допомогою можна зробити добре та швидко.

Залишити коментар

Вашу електронну адресу не буде оприлюднено. Обов’язкові поля позначено *