Использование py.test с компилированным библиотечным кодом

У меня есть библиотека python со следующей структурой репозитория:

repobase
 |- mylibrary
 |  |- __init__.py
 |- tests
    |- test_mylibrary.py

До сих пор запуск тестов можно было просто выполнить, вызвав py.test в каталоге repobase. import mylibrary в test_mylibrary.py затем использовал локальный код в repobase/mylibrary.

Теперь я расширил библиотеку, чтобы использовать скомпилированный код. Поэтому исходный код в repobase/mylibrary не работает сам по себе. Я должен сделать setup.py build. Это создает repobase/build/lib.linux-x86_64-2.7/mylibrary.

Есть ли разумный способ заставить py.test использовать этот каталог для импорта mylibrary? Учитывая эти ограничения:

  • Я не хочу включать никакую магию sys.path/import в test_mylibrary.py, потому что это может нарушить тесты в других envrionments.

  • Я не хочу отказаться от возможности запускать py.test из repobase. Поэтому изменение PYTHONPATH не помогает, потому что . по-прежнему будет первым в sys.path. И таким образом repobase/mylibrary будет предпочтительнее repobase/build/lib.linux-x86_64-2.7/mylibrary.

Если нет, то какой стандартный способ для тестирования библиотек python, которые нуждаются в создании?

Ответ 1

Я думаю, ваша проблема в том, что py.test не копирует встроенный общий объект в корень вашего репозитория.

Я просто попытался запустить UT прямо из Python wiki при тестировании расширений C с помощью py.test следующим образом:

python setup.py build
py.test test/examp_unittest.py

Это не удалось с помощью AssertionError: No module named examp.

Однако, когда я следую wiki в письме (и запускаю python setup.py test вместо этого), я отмечаю, что он копирует .so в корневой каталог (обратите внимание на последнюю строку до начала запуска теста):

running test
running egg_info
writing examp.egg-info/PKG-INFO
writing top-level names to examp.egg-info/top_level.txt
writing dependency_links to examp.egg-info/dependency_links.txt
reading manifest file 'examp.egg-info/SOURCES.txt'
writing manifest file 'examp.egg-info/SOURCES.txt'
running build_ext
copying build/lib.linux-x86_64-2.6/examp.so ->
runTest (test.examp_unittest.DeviceTest) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Запустив это в моей системе, я могу теперь запустить py.test довольно счастливо на той же базе кода, как показано ниже.

============================= test session starts ==============================
platform linux2 -- Python 2.7.3, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /tmp/sotest, inifile: 
collected 1 items 

test/examp_unittest.py .

=========================== 1 passed in 0.01 seconds ===========================

Таким образом, решение состоит в том, чтобы скопировать общий объект в корень вашего репозитория.

Чтобы убедиться, что я выполнил все это с нуля, просто создайте расширение, скопируйте общий объект и запустите py.test. Все это работает как ожидается.

Ответ 2

Из обсуждения в чате это звучит так, как будто реализация C предоставляет только часть функциональности реализации Python.

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

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

Предположим, что ваш макет выглядит так...

repobase
 |- image
 |  |- __init__.py
 |  |- pyJPEG.py
 |- build
 |  |- lib.linux-x86_64-2.7
 |     |- cJPEG.so
 |- tests
    |- test_image.py

... ваш PYTHONPATH включает /path/to/repobase:/path/to/repobase/build/lib.linux-x86_64-2.7, ваш cJPEG.so экспортирует символы jpeg_decompress и jpeg_compress, и ваши файлы выглядят так:

изображение /__ __ INIT. Ру

# Load the C implementation if we have it, otherwise fall back to
# a pure Python implementation
try:
    from cJPEG import jpeg_decompress, jpeg_compress
except ImportError:
    from pyJPEG import jpeg_decompress, jpeg_compress

def load_image(filename):
    data = open(filename, 'rb').read()
    if filename.endswidth('.jpg'):
        return jpeg_decompress(data)
    else:
        raise NotImplementedError

def save_image(data, filename, filetype='JPEG'):
    if filetype == 'JPEG':
        data = jpeg_compress(data)
    else:
        raise NotImplementedError
    open(filename, 'wb').write(data)

изображение /pyJPEG.py

def jpeg_decompress(data):
    # A pure Python implementation of a JPEG decoder

def jpeg_compress(data):
    # A pure Python implementation of a JPEG encoder

При таком макете тестовому набору не важно, построена ли библиотека или нет - вы можете использовать один и тот же набор в обоих случаях, а наличие (или отсутствие) cJPEG.so будет определять, какая версия протестирована.