Как фреймворк Pytest упрощает тестирование кода
Как фреймворк Pytest упрощает тестирование кода
Рассказываем, почему разработчики и тестировщики любят этот инструмент и предпочитают его другим фреймворкам.
В стандартной библиотеке Python есть набор инструментов для написания тестов. Он подходит для простых задач — проверки работы отдельных функций или небольших классов: например, если нужно убедиться, что функция правильно складывает числа или небольшой класс правильно обрабатывает данные. Но когда проект усложняется, а количество тестов растёт, нужны более гибкие инструменты — например, чтобы проводить тесты с разными наборами данных или проводить выборочные тестирования.
Один из таких инструментов — фреймворк для тестирования кода Pytest. Он позволяет писать тесты в виде простых функций, а не классов, использует всего одну команду для проверки условий — assert. Это делает код читаемым, а тесты — понятными. К тому же для фреймворка есть множество плагинов, которые упрощают работу с ним.
С помощью Pytest можно тестировать:
Представим, что программист разработал функцию, которая суммирует элементы списка. Ему нужно убедиться, что она работает правильно. Разберём, как разработчик будет действовать с Pytest и без этого фреймворка.
Фреймворки для тестирования используют не только разработчики, но и тестировщики-автоматизаторы. От классических тестировщиков они отличаются знанием языков программирования и более глубоким пониманием работы ПО. Например, чтобы использовать Pytest, тестировщику нужно освоить язык программирования Python. С этого начинается курс «Автоматизатор тестирования на Python». Студенты изучают основы языка и принципы объектно ориентированного программирования. Затем приступают к написанию простых юнит-тестов и постепенно учатся организовывать тестирование веб-приложений.
Самый простой способ установить 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 позволяют использовать общий код в разных тестах.
Представим простую функция, которая читает данные из файла:
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 == ''
Что изменилось:
Код можно улучшить, если добавить класс 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 — это что-то вроде ярлыков, которые можно прикрепить к тестам, которые нужно запустить.
Посмотрим, как это работает на практике. Создадим файл 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"
Пояснения к коду:
Чтобы запустить тесты с меткой 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.
Разберём несколько продвинутых вариантов использования меток.
Читать также: