Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Тестирование кода, взаимодействующего с файлами Python: Продвинутое тестирование

Наиболее типичный побочный эффект – взаимодействие с файлами (файловые операции). В основном это либо чтение файлов, либо запись в них. С чтением разбираться значительно проще, поэтому с него и начнём.

Чтение файлов

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

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

# Функция читает файл со списком пользователей системы и возвращает их имена
# В линуксе это файл /etc/passwd
user_names = read_user_names()

В тестах читать /etc/passwd нельзя, потому что содержимое этого файла зависит от окружения, в котором запущены тесты. Для тестирования нужно создать файл аналогичной структуры в фикстурах и указать его при запуске функции:

def test_read_user_names():
    # fixtures/passwd
    passwd_path = 'fixtures/passwd'
    user_names = read_user_names(passwd_path);
    assert user_names ==  # ожидаемый результат

Запись файлов

С записью файлов уже сложнее. Главная проблема – отсутствие гарантированной идемпотентности. Это значит, что повторный вызов функции, записывающей файлы, может вести себя не как первый вызов, например, завершаться с ошибкой, либо приводить к другим результатам.

Почему? Представьте себе, что мы пишем тесты на функцию log(message), которая дописывает все переданные в неё сообщения в файл:

log = Logger('development.log')
log('first message');
# Смотрим содержимое файла
# cat development.log
# first message
log('second message')
# cat development.log
# first message
# second message

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

  • Наверняка внутри этой функции процесс создания файла это особый случай, который нужно тестировать отдельно. Повторные запуски тестов перестанут проверять эту ситуацию.
  • Сложнее написать предсказуемый тест. Придётся дополнительно придумывать хитрые схемы, например проверять только последнюю строку в файле. Такой подход понижает качество теста.
  • Не особенно критично, но всё же: в процессе запуска тестов появляется файл, который постоянно растёт в размерах.

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

import pytest
import os

filepath = 'some/file/path'

# Будет вызываться для каждого теста
@pytest.fixture(autouse=True)
def clean_file():
    if os.path.isfile(filepath):
        os.remove(filepath)
    yield

Но даже ручное удаление файлов, это сложное решение, которое требует постоянного контроля происходящего. Придется все время об этом помнить. Чтобы не приходилось этим заниматься, программисты опираются на особенности работы файловой системы в Linux. С помощью специальной функции создается временная директория с уникальным именем, затем все действия делаются внутри. Каждое создание такой директории гарантирует уникальное имя. Удалять такие директории не нужно, так как операционная система сама подчищает их.

# content of test_tmpdir.py
def test_create_file(tmpdir):
    p = tmpdir.mkdir("sub").join("hello.txt")
    p.write("content")
    assert p.read() == "content"
    assert len(tmpdir.listdir()) == 1

Виртуальная файловая система (ФС)

Это ещё один способ тестировать код, работающий с ФС. С помощью специальной библиотеки во время тестов создаётся виртуальная файловая система. Она автоматически подменяет реальную файловую систему для всех модулей работающих с файловой системой. Это значит, что функцию, которая тестируется, трогать не надо. Эта функция продолжает думать, что она работает с реальным диском. Вся конфигурация при этом задаётся снаружи:

# Этот способ даёт идемпотентность из коробки
def my_fakefs_test(fs):
    # "fs" – фикстура для управления виртуальной файловой системой
    fs.create_file('/var/data/xx1.txt')
    assert os.path.exists('/var/data/xx1.txt')

Дополнительные материалы

  1. Временные директории и файлы в Pytest

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 130 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Python-разработчик
Профессия
Разработка веб-приложений на Django
25 мая 10 месяцев

Используйте Хекслет по максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»