Программирование • 12 сентября 2024 • 5 мин чтения

Как фреймворк Pytest упрощает тестирование кода

Рассказываем, почему разработчики и тестировщики любят этот инструмент и предпочитают его другим фреймворкам.

Что такое Pytest

В стандартной библиотеке Python есть набор инструментов для написания тестов. Он подходит для простых задач — проверки работы отдельных функций или небольших классов: например, если нужно убедиться, что функция правильно складывает числа или небольшой класс правильно обрабатывает данные. Но когда проект усложняется, а количество тестов растёт, нужны более гибкие инструменты — например, чтобы проводить тесты с разными наборами данных или проводить выборочные тестирования.

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

С помощью Pytest можно тестировать:

  • Функции — например, проверять, правильно ли суммируются числа.
  • Классы и методы — например, проверить, что банковский счёт правильно создаётся с нужным балансом, корректно пополняется и позволяет снимать деньги, не допуская отрицательного баланса. Pytest помогает структурировать тесты для каждого метода, упрощая их поддержку и проверку правильности работы кода.
  • API — чтобы убедиться, что сервер возвращает правильные ответы на запросы.
  • Базы данных — проверить, что запросы к базе данных возвращают правильные результаты.

Представим, что программист разработал функцию, которая суммирует элементы списка. Ему нужно убедиться, что она работает правильно. Разберём, как разработчик будет действовать с Pytest и без этого фреймворка.


Вручную


С Pytest

Пишет отдельные скрипты для тестирования функции, а затем вручную проверяет результаты. Это отнимает много времени и усилий
Создаёт файл с тестами один раз и запускает Pytest, который автоматически проверяет результаты
Каждый раз, когда он будет изменять код, придётся вручную запускать функцию с разными входными данными и проверять результаты
Добавляет тестовую функцию, которая проверяет результат
Разработчик сам должен разобраться, прошёл тест или нет. Если что-то не так — исправить ошибку и снова запустить скрипт
Запускает Pytest и сразу видит, прошёл он или нет. Pytest предоставляет подробные отчёты о результатах тестирования, показывая, какие тесты прошли, а какие нет

Фреймворки для тестирования используют не только разработчики, но и тестировщики-автоматизаторы. От классических тестировщиков они отличаются знанием языков программирования и более глубоким пониманием работы ПО. Например, чтобы использовать Pytest, тестировщику нужно освоить язык программирования Python. С этого начинается курс «Автоматизатор тестирования на Python». Студенты изучают основы языка и принципы объектно ориентированного программирования. Затем приступают к написанию простых юнит-тестов и постепенно учатся организовывать тестирование веб-приложений.

Станьте автоматизатором тестов на Python за 5 месяцев

Освоите pytest, Selenium WebDriver и другие инструменты, научитесь строить процесс автоматизации, будете учиться у тестировщиков из Яндекса.

Преимущества и недостатки


Плюсы


Минусы

Простота использования. Pytest легко начать использовать. Не нужно изучать много новых команд или сложных понятий, чтобы писать тесты. Например, чтобы проверить, правильно ли функция суммирует числа, достаточно написать всего несколько строк кода:

def test_sum():
assert sum([1, 2, 3]) == 6

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

Порог входа для Pytest выше, чем для unittest: потребуется время, чтобы вникнуть в функциональное программирование.

Читаемость. Тесты, написанные с использованием Pytest, понятны и легко читаемы. Это делает их доступными не только для автора, но и для других членов команды. Например, тест на проверку правильности суммы чисел выглядит как обычная функция Python, что облегчает понимание и поддержку.

Совместимость с другими фреймворками. Если в проекте уже используется другой фреймворк для тестирования, с ним запустить тест Pytest не получится.

Расширяемость. Pytest поддерживает плагины, которые добавляют новые функции и упрощают тестирование. Например, если нужно тестировать работу с базой данных, можно установить соответствующий плагин, который поможет настроить и очистить базу данных перед тестами и после них.

Мощные возможности. Фикстуры позволяют подготовить тестовую среду один раз и использовать её в нескольких тестах. Метки помогают организовать тесты и выбирать, какие тесты запускать. Например, продолжительные тесты помечают как slow и запускают отдельно от быстрых.

Установка и настройка Pytest

Самый простой способ установить Pytest — c помощью команды pip в терминале:

pip install -U pytest

Так выглядит установка Pytest в Windows. Она начнётся не сразу, а примерно через минуту

Для установки можно использовать poetry — инструмент для управления зависимостями и пакетами в проектах на Python. Он помогает легко устанавливать нужные библиотеки и следить за тем, чтобы все версии этих библиотек были совместимы друг с другом. Вот так будет выглядеть установка pytest с помощью poetry:

poetry add --dev pytest

Специальной настройки Pytest не требует.

Как писать тесты

Проверим функцию, которая определяет, является ли число чётным.

1. Создаём Python-файл. Это можно сделать в среде разработки (IDE) или в текстовом редакторе вроде «Блокнота». Назовём его test_example.py. Важно, чтобы расширение было .py, — так pytest поймёт, что имеет дело с python-файлом.

2. Пишем код теста. Чётное число делится на 2 без остатка, а нечётное — с остатком. Сначала мы создаём функцию is_even, которая принимает на вход одно число и возвращает True, если число чётное, и False, если нет. number % 2 — это выражение делит число на 2 и возвращает остаток. Если остаток равен 0, значит, число чётное, и функция возвращает True. Если остаток не равен 0, значит, число нечётное, и функция возвращает False.

Готовый код выглядит так:

def is_even(number):
return number % 2 == 0

def test_is_even():
assert is_even(2) == True # 2 — чётное число
assert is_even(3) == False # 3 — нечётное число
assert is_even(0) == True # 0 — считается чётным числом
assert is_even(-4) == True # -4 — чётное число

Тест можно написать даже в «Блокноте»

3. Открываем консоль и указываем путь к папке, в которой сохранили файл с кодом. Например: C:\Users\hp\Desktop\test_py. Так pytest не будет перебирать файлы по всему рабочему столу, а сразу обратится к конкретной папке.

4. С помощью команды pytest запускаем проверку:

pytest

5. Pytest найдёт файл test_example.py, увидит внутри него функцию, которая начинается с test_, и автоматически запустит её. Если все утверждения assert верны (то есть функция is_even работает правильно), появится сообщение о том, что все тесты прошли успешно. Если какой-то из тестов не пройдёт, pytest покажет, где именно возникла ошибка.

Тест прошёл успешно

Теперь поменяем один из примеров — вместо нечётной тройки поставим чётную восьмёрку:

def is_even(number):
return number % 2 == 0

def test_is_even():
assert is_even(2) == True # 2 — чётное число
assert is_even(8) == False # 8 — нечётное число
assert is_even(0) == True # 0 — считается чётным числом
assert is_even(-4) == True # -4 — чётное число

Pytest выдал сообщение об ошибке

Что такое фикстуры в Pytest

Фикстуры — это специальные функции, которые помогают подготовить окружение для тестов и убрать тестовые данные после их выполнения.

Когда пишут несколько тестов для одного модуля или функции, часто они требуют одинаковой подготовки. Например:

  • подключение к базе данных;
  • создание или удаление временных файлов;
  • настройка начальных данных.

Если писать такой подготовительный код в каждом тесте, он будет повторяться и занимать много места. Это делает код сложнее. Фикстуры Pytest позволяют использовать общий код в разных тестах.

Представим простую функция, которая читает данные из файла:

def read_data_from_file(file_path):
with open(file_path, 'r') as file:
return file.read()

Тестируем без фикстур

Код будет выглядеть так:

def test_read_data_from_file():
# Подготовка
file_path = '/tmp/testfile.txt'
with open(file_path, 'w') as f:
f.write('Hello, World!')

# Тестирование
data = read_data_from_file(file_path)
assert data == 'Hello, World!'

# Уборка
import os
os.remove(file_path)

def test_read_data_from_empty_file():
# Подготовка
file_path = '/tmp/emptyfile.txt'
with open(file_path, 'w') as f:
pass # Создаём пустой файл

# Тестирование
data = read_data_from_file(file_path)
assert data == ''

# Уборка
import os
os.remove(file_path)

В каждом тесте создаём временный файл (подготовка), проводим тестирование и удаляем файл после теста (уборка). Этот код повторяется, что делает его более громоздким и трудно поддерживаемым.

Тестируем с фикстурами

Как можно упростить этот код с помощью фикстур:

import pytest
import os

@pytest.fixture
def temp_file(tmp_path):
file_path = tmp_path / 'testfile.txt'
with open(file_path, 'w') as f:
f.write('Hello, World!')
yield file_path
# Очистка после теста
os.remove(file_path)

@pytest.fixture
def empty_file(tmp_path):
file_path = tmp_path / 'emptyfile.txt'
open(file_path, 'w').close() # Создаём пустой файл
yield file_path
# Очистка после теста
os.remove(file_path)

def test_read_data_from_file(temp_file):
data = read_data_from_file(temp_file)
assert data == 'Hello, World!'

def test_read_data_from_empty_file(empty_file):
data = read_data_from_file(empty_file)
assert data == ''

Что изменилось:

  1. Вынесли код подготовки файлов в фикстуры temp_file и empty_file. Теперь они создают временные файлы и возвращают путь к ним. Эти файлы автоматически удаляются после завершения теста благодаря использованию временной директории tmp_path.
  2. Использовали ключевое слово yield в фикстурах temp_file и empty_file. Добавление yield и последующая очистка делают тесты безопаснее: временные файлы удаляются автоматически после выполнения тестов, предотвращая накопление ненужных файлов в системе. Теперь фикстуры сначала выполняют свою задачу (создание файлов), затем передают путь к файлу в тест через yield. После завершения теста код удаляет созданные файлы.
  3. Тесты используют фикстуры как параметры. Pytest автоматически подставляет результат выполнения фикстуры (temp_file или empty_file) в соответствующий тест.
  4. В тестах больше нет повторяющегося кода для подготовки — только вызовы фикстур и проверка результата (assert). Если нужно изменить процесс подготовки или очистки, достаточно внести изменения только в фикстуру, а не во все тесты.

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

Обновлённый код:

import pytest
import os
from io import BytesIO

@pytest.fixture
def temp_file():
file_obj = BytesIO() # Создаём объект BytesIO для эмуляции файла в памяти
file_obj.write(b'Hello, World!') # Записываем строку в файл как байты
file_obj.seek(0) # Перемещаем указатель в начало файла для последующего чтения
yield file_obj
# Очистка не требуется, объект BytesIO автоматически освобождает память

@pytest.fixture
def empty_file():
file_obj = BytesIO() # Создаём пустой объект BytesIO, эмулирующий пустой файл
yield file_obj
# Очистка не требуется, объект BytesIO автоматически освобождает память

def test_read_data_from_file(temp_file):
data = temp_file.read().decode() # Читаем данные из файла и декодируем из байтов в строку
assert data == 'Hello, World!'

def test_read_data_from_empty_file(empty_file):
data = empty_file.read().decode() # Читаем данные из пустого файла и декодируем
assert data == ''

Какие в Pytest есть тестовые метки

Бывают ситуации, когда нужно запускать не все тесты, а только некоторые. Например:

  • Те, которые проверяют функциональность регистрации пользователя.
  • Тесты, которые нужно проводить долго.
  • Тесты только для определённого модуля программы.

Тестовые метки в Pytest — это что-то вроде ярлыков, которые можно прикрепить к тестам, которые нужно запустить.

Посмотрим, как это работает на практике. Создадим файл test_example.py и добавим в него несколько тестов с метками.

import pytest
# Тест с меткой "user"
@pytest.mark.user
def test_create_user():
assert "user" == "user"

# Тест с меткой "slow"
@pytest.mark.slow
def test_heavy_computation():
result = sum(i for i in range(1000000))
assert result > 0

# Тест с двумя метками "user" и "slow"
@pytest.mark.user
@pytest.mark.slow
def test_update_user_profile():
assert "profile updated" == "profile updated"

Пояснения к коду:

  • Метка @pytest.mark.user добавлена к тестам test_create_user и test_update_user_profile, чтобы обозначить, что они связаны с пользователями.
  • Метка @pytest.mark.slow добавлена к тестам test_heavy_computation и test_update_user_profile, чтобы указать, что они могут выполняться долго.
  • У test_update_user_profile две метки, потому что этот тест и связан с пользователями, и может быть медленным.

Чтобы запустить тесты с меткой user, в командной строке вводим pytest -m user. Pytest запустит только тесты с меткой user — test_create_user и test_update_user_profile.

Чтобы запустить тесты с меткой slow, в командной строке вводим pytest -m slow. Pytest запустит тесты с меткой slow — test_heavy_computation и test_update_user_profile.

Если нужно запустить тесты c обеими метками, используем команду pytest -m "user and slow". Запустится только test_update_user_profile.

Разберём несколько продвинутых вариантов использования меток.


Запуск всех тестов, кроме определённых меток. Например, если нужно запустить все тесты, кроме медленных

pytest -m "not slow"

Запуск тестов, которые имеют хотя бы одну из меток

pytest -m "user or slow"

Комбинирование меток для сложного отбора тестов. Например, есть тесты с метками api и slow, и нужно запустить только те, которые относятся к API и выполняются быстро.

pytest -m "api and not slow"

Совет эксперта

Артём Стрельцов
Качественные тесты — залог «здорового» кода. Ни один хороший проект в крупной компании не обходится без тестов, и это правильно. Расстраивать пользователей багом на ровном месте не хочется, а проводить ручной регресс в рамках каждого релиза — утомительно. Самое волнительное — когда вы пишете какую-нибудь популярную библиотеку, и от качества ваших тестов зависят сотни разных решений в разных компаниях.

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

Статью подготовили:
Артем Стрельцов
Яндекс Практикум
Разработчик
Яндекс Практикум
Редактор

Дайджест блога: ежемесячная подборка лучших статей от редакции

Поделиться

Успейте начать учебу в Практикуме до конца ноября со скидкой 20%

Thu Sep 26 2024 02:45:51 GMT+0300 (Moscow Standard Time)