Рекомендации по интеграции

Установка пакета с помощью pip

При разработке мы рекомендуем использовать venv для создания виртуального окружения и pip для установки вашего приложения и любых других используемых пакетов (таких как сам pytest). Это гарантирует, что ваш код и окружение будут изолированы от вашей системной установки Python.

Затем поместите в корень вашего проекта файл setup.py, в котором ,как минимум, должен быть следующий код:

from setuptools import setup, find_packages

setup(name="PACKAGENAME", packages=find_packages())

Здесь PACKAGENAME - имя вашего пакета. Затем вы можете установить ваш пакет в режиме редактирования, запустив в той же директории:

pip install -e .

Это позволит вам менять код исходных файлов (как тестов, так и самого приложения) и перезапускать тесты по вашему желанию. Вы выполняете аналог python setup.py develop или conda develop и устанавливаете ваш пакет, используя символическую ссылку на разрабатываемый код.

Соглашения Python по поиску тестов

Стандартный поиск тестов pytest выполняется по следующим правилам:

  • Если не переданы никакие параметры, поиск начинается в testpaths (если они настроены) или в текущей директории. Кроме того, можно использовать параметры командной строки с любой комбинацией директорий, имен файлов и идентификаторов узлов.

  • Проводится рекурсивный поиск в директориях, если они не соответствуют шаблону norecursedirs.

  • Ищутся файлы с именами «test_*.py» or «*_test.py», импортированные по имени для импорта.

  • Из этих файлов выбираются:

    • функции и методы с префиксом «test», расположенные вне классов

    • тестовые функции и методы с префиксом «test» внутри классов с префиксом «Test» (без метода «__init__»)

Пример настройки поиска тестов: Изменение стандартных правил поиска тестов Python.

В модулях Python pytest для поиска тестов также использует стандартный механизм подклассов unittest.TestCase.

Выбор шаблона размещения и правила импорта тестов

pytest поддерживает два способа размещения тестов:

Тесты вне кода самого приложения

Размещение тестов в отдельном каталоге вне фактического кода приложения может быть полезно, если у вас много функциональных тестов или же по каким-то причинам вы хотите держать тесты отдельно от фактического кода приложения (зачастую это - хорошая идея):

setup.py
mypkg/
    __init__.py
    app.py
    view.py
tests/
    test_app.py
    test_view.py
    ...

Такой способ дает вам следующие преимущества:

  • Вы можете тестировать установленную версию приложения после выполнения pip install.

  • Вы можете тестировать локальную копию приложения в режиме редактирования после выполнения pip install --editable.

  • Если у вас нет setup.py-файла и вы пользуетесь тем, что Python по умолчанию помещает текущий каталог в sys.path для импорта вашего пакета, вы можете выполнить python -m pytest, чтобы запустить тестирование локальной копии напрямую, без использования pip.

Примечание

См. sys.path/PYTHONPATH и механизм импорта pytest чтобы узнать больше о разнице между вызовом pytest и python -m pytest.

Обратите внимание, что при использовании такой схемы ваши тестовые файлы должны иметь уникальные имена, поскольку pytest просто добавляет директорию tests/ в sys.path и полное имя файла будет представлять собой просто имя вашего модуля.

Если все же нужно запускать модули с одинаковыми именами, можно добавить файл __init__.py в папку tests и ее подпапки, разбив ваши тесты на пакеты:

setup.py
mypkg/
    ...
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

В этом случае pytest позволяет использовать одинаковые имена для файлов, поскольку на самом деле будут запускаться файлы с именами tests.foo.test_view и tests.bar.test_view. Однако здесь есть одна тонкость: чтобы загрузить тестовые модули из директории tests, pytest прописывает корень репозитария в переменную sys.path, что может иметь некоторые побочные эффекты, так как директория mypkg тоже импортируется. Это может создавать проблемы, если вы используете для тестирования вашего пакета в виртуальной среде такие инструменты, как tox, поскольку вам нужно тестировать установленную версию пакета, а не локальный код репозитария.

В таких случаях настоятельно рекомендуется использовать шаблон src, когда корневой пакет приложения находится в поддиректории корневой директории:

setup.py
src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

Использование такого шаблона помогает обойти множество подводных камней и имеет массу преимуществ, которые прекрасно освещены в замечательном блоге: blog post by Ionel Cristian Mărieș.

Тесты как часть кода приложения

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

setup.py
mypkg/
    __init__.py
    app.py
    view.py
    test/
        __init__.py
        test_app.py
        test_view.py
        ...

В этом случае проще всего запустить тесты, используя опцию --pyargs:

pytest --pyargs mypkg

pytest найдет, где установлен mypkg и соберет тесты оттуда.

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

Примечание

Вы можете использовать пространство имен Python3 (PEP420) для своего приложения, но pytest все равно будет искать имя для импорта, основываясь на наличии файлов «__init__.py». Если вы используете один из рекумендуемых способов формирования файловой системы, но не хотите использовать файлы «__init__.py» в ваших директориях, эта схема будет работать на версиях Python3.3 и выше. Однако при «встроенных» тестах вам придется использовать абсолютный импорт, чтобы добраться до кода вашего приложения.

Примечание

Когда pytest во время рекурсивного обхода файловой системы находит файл вида «a/b/test_module.py», он определяет имя для импорта следующим образом:

  • определяется basedir: это первый, самый высоко расположенный в корне каталог, в котором нет файла __init__.py. Т. е., если и a, и b содержат файлы __init__.py, то родительская директория a станет basedir.

  • выполняется sys.path.insert(0, basedir) чтобы задать полное имя для импорта

  • выполняется import a.b.test_module, где путь определяется преобразованием разделителей / в символы .. Это означает, что нужно сопоставлять именя файлов и директорий импортируемым именам напрямую.

Причина такой эволюции метода импорта заключается в том, что в крупных проектах тестовые модули могут импортировать друг друга, и получение канонического имени импорта помогает избежать сюрпризов (например, двойного импорта тестового модуля).

tox

Если работа выполнена и вы хотите убедиться, что готовый пакет проходит все тесты, можно обратить внимание на tox - инструмент автоматизации тестирования и его поддержку pytest. tox помогает настроить виртуальное окружение с заранее заданными зависимостями и затем запускать предварительно настроенную команду тестирования с различными опциями. tox тестирует установленный пакет, а не источник кода, тем самым помогая найти погрешности сборки.