Как новичку начать писать юнит-тесты на Python

Вот что сгенерила нейросеть на фразу "как новичку начать писать юнит-тесты на Python"

Давно хотел написать серию статей, касаемо тестирования с позиции разработчика. В этой "открывающей" статье пробежимся по основным моментам написания юнитов, которые больше всего будут полезны джунам, которые уже хотят писать код "по-взрослому", а значит - покрывать тестами. А в конце посмотрим, что думает про юнит-тестирование AI. Будет интересно, поехали!

Шаг 1. Выбор фреймворка

Чтобы не писать свои велосипеды, мир Python предоставляет набор разнообразных фреймворков на выбор:

  1. стандартный модуль unittest, поставляемый уже сразу с дистрибутивом Python. Содержит определенное количество нужных примитивов, Mock-объектов, patch-заглушек и тд. Особенно радует наличие асинхронных моков - AsyncMock.
  2. Pytest, являющийся де-факто стандартом для тестирования (https://docs.pytest.org/) и имеющий кучу различных плагинов на разные случаи жизни.
  3. mockito - шикарный spying framework, который вместе с плагином pytest-mockito позволяет не просто проверять кейсы, но и отслеживать состояние, в котором assert не сработал. https://pypi.org/project/mockito/

Шаг 2. Создание отдельного тест-файла

* Ниже код будем рассматривать на примере pytest.

Файл должен начинаться с префикса "test_", например test_calc_functions.py. Имя файла должно подсказывать, что мы внутри него тестируем. Также необходимо убедиться, что чувствительный production-код, личные данные не попали в ваш тест-код.3. Написание тест-функции.

Шаг 3. Написание тест-функции

Тест-функция также должна начинаться с префикса "test_" и следовать тому, что она тестирует. Она должна включать assert блоки, которые проверяют соответствует ли результат выполнения тестируемой функции ожидаемому результату (expected result).

В большинстве кейсов мы используем оператор assert, который проверяет истинность выражения. Истинность выражения может выражаться в логической операции сравнения, результатом выполнения функции и тд.

Пример

>>> eval('2+2') == 4 True >>> assert eval('2+2') == 4 >>> assert 2 == 2

При попытке явно вызвать assert на невалидном выражении - получим ошибку

>>> assert eval('2+2') != 4 Traceback (most recent call last): File "<input>", line 1, in <module> AssertionError

Кейс, когда результат соответствует нашим ожиданиям называется позитивным

Напишем пользовательскую функцию и позитивный тест-кейс (помним про префикс test_).

def add(x: int, y: int) -> int: return x + y def test_add(): assert add(2, 3) == 5

Шаг 4. Запуск тестов

Используя фреймворк вы можете запускать свои тесты и проверять результаты на успех/ошибку. Конечно, можно запускать тесты и без фреймворка, на чистом python, используя assert. Но тогда многие возможности будут недоступны.

Если все тесты прошли успешно - фреймворк сообщит об этом зеленым выводом в консоль и сообщением. Если тесты упали - вывод будет красным. Необходимо поправить тест, либо исправить реализацию и повторить запуск снова.

Здесь я запускаюсь из-под виртуального окружения, вам же достаточно в консоли просто запустить

% pytest -s .

Сложные моменты для начинающих

При проведении занятий, заметил такую вещь - новичкам сложно сформулировать тест-кейс, ведь тут нужно написать проверку на проверку. Ниже приведу личные советы, как поправить эту ситуацию.

Первое - понять, что ты тестируешь? Для того, чтобы писать не формальные тесты, а качественные и вдумчивые - необходимо понимать ту доменную область, в которой этот код будет работать. Возвращаясь к ф-и сложения add - необходимо ответить на вопросы:

  • как будет вызываться эта функция? сама или внутри другой?
  • что эта функция вообще делает? что она НЕ делает?
  • какие аргументы могут быть переданы? как должна вести себя при передачи аргументов другого типа? диапазон допустимых значений
  • какие исключения может вызывать: стандартные или пользовательские?

Второе - понять, как ты будешь это тестировать? Будет ли это набор ручных тестов, или отдельное семейство юнит-тестов на фреймворке? Какие зависимости нужны? Как будут выглядеть тестовые данные, в каком формате?

Особенно стоит продумать негативные кейсы. Здесь попытаемся вызвать функцию add с невалидным аргументом-строкой. Очевидно, что если вызвать ее в боевом коде, получим ошибку невалидного типа.

add('hello', 2) Traceback (most recent call last): File "<input>", line 1, in <module> File "/Users/vladworld/Documents/code/own/pycourse_by_vladworld_for_beg/articles/test_example.py", line 5, in add return x + y TypeError: can only concatenate str (not "int") to str

Обернув код библиотечным контекстным менеджером, мы получим валидный негативный тест. В нем мы ожидаем возникновение исключения, поэтому он пройдет успешно.

# test_example.py import pytest def test_negative_case(): with pytest.raises(TypeError): add('hello', 2)

В этом и суть негативных кейсов - успешный неуспех :)

Что же думает AI по этому поводу?

Вот что мне сгенерил Kandinsky

Но более всего понравился Шедеврум

Действительно, написание хороших читаемых тестов - это искусство ходьбы между болотами и лесами по пути домой.

Спасибо, что дочитали! Жду в следующий раз!

🖤 Подписывайтесь на мою телегу. Больше кода 🐍 - меньше багов 🪲!

0
Комментарии
-3 комментариев
Раскрывать всегда