Программирование

Мой первый опыт написания тестов на 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% дало мне абсолютную уверенность в том, что основа программы надежна, как швейцарские часы.

Агенты

Конечно все было совсем не так) Было быстрей и проще вместо того чтобы потратить много дней, если бы я не использовал ИИ и агента.

Конечно агент может написать тесты, даже если вы вообще не будете понимать ничего и проверять после него, но я его использовал для быстрого старта — просил рассказать о тестах, он показывал примеры и рассказывал концепции, я расспрашивал разные технические моменты — как лучше и откуда берутся данные о покрытии кода тестами. Это все оказалось не только интересно но и полезно для лучше понимания что тестировать и как. Конечно я не могу сказать что буду когда либо их писать вручную, все же это медленная рутина, а агенты в конце концов и нужны для этого, чтоб делать то что может быть сделано хорошо и быстро при помощи их.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *