Файловый тест оставляет мусор: проверяем сохранение через tmp_path

Тест сохранения задач пишет файл tasks.json рядом с исходниками. После падения остаётся старый файл, и следующий запуск начинает проверять случайное состояние. При параллельном выполнении два теста могут писать в одно место. Подмена open на mock убирает конфликт, но заодно перестаёт проверяться реальная запись JSON и кодировка.

Fixture tmp_path предоставляет отдельный pathlib.Path для каждого теста. В этой директории можно создавать обычные файлы и использовать настоящий файловый адаптер. Тест описывает собственное начальное состояние и не требует уборки через unlink в конце. Результат предыдущего сценария не становится входными данными следующего.

Настоящий файл в изоляции

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

import json
from pathlib import Path

def save(path: Path, tasks: list[str]) -> None:
    path.write_text(json.dumps(tasks, ensure_ascii=False), encoding="utf-8")

def load(path: Path) -> list[str]:
    return json.loads(path.read_text(encoding="utf-8"))

def test_round_trip(tmp_path):
    path = tmp_path / "tasks.json"
    save(path, ["Отчёт", "review"])
    assert path.is_file()
    assert load(path) == ["Отчёт", "review"]
    assert json.loads(path.read_text(encoding="utf-8")) == ["Отчёт", "review"]

Проверка формата и отказов

Установите pytest: python -m pip install pytest. Файл test_storage.py запускается командой python -m pytest -q test_storage.py. Проверяем одновременно содержимое после чтения и реальное наличие файла. Русское название заставляет пройти через явно выбранную UTF-8, а не довольствоваться ASCII-примером, который скрывает проблемы кодировки.

Round-trip полезен, но сам по себе может пропустить согласованную ошибку писателя и читателя. Если оба одинаково искажают формат, тест проходит. Для публичного формата добавьте проверку JSON независимым чтением или сравните с согласованным представлением. Если порядок полей не важен, сравнивайте разобранные значения, а не всю строку с пробелами.

Проверьте отказ отдельно: путь указывает в отсутствующий родительский каталог, файл содержит повреждённый JSON, значение имеет неожиданную структуру. Решите, кто создаёт родительские каталоги и кто переводит OSError в ошибку приложения. tmp_path позволяет собрать такие состояния непосредственно в тесте, без глобальных фикстур и заранее подготовленного дерева файлов.

Не стоит проверять точное имя каталога, который выбрал pytest. Это служебная деталь. При падении можно посмотреть сохранённый файл в директории тестового запуска; политика удержания временных каталогов настраивается pytest. Для проверки поведения достаточно использовать полученный Path. Так тест остаётся привязан к формату и результату записи, а не к машине, на которой его выполнили.

Следующий шаг — перенести этот приём из отдельного файла в поддерживаемое приложение. В курсе «Python для продвинутых: ООП, типизация и тестирование» структура пакетов, объектная модель, mypy и pytest соединяются в типизированном трекере задач с хранением данных и логированием. Можно отработать связь между интерфейсом, реализацией и регрессионным тестом, а затем применить её к своему проекту.

Посмотреть программу и практику курса.

Документация: официальный источник по теме.

Другие задачи по теме: Типы, тесты и поведение программы.

Продолжить на практике

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.