3

我使用 Poetry 来构建我的带有 cython 扩展的包。现在我想为它写测试(最好用nosetest)。问题是我需要预编译通常使用的二进制文件setup.py build_clib build_ext --inplace

对我来说最好的解决方案是运行测试而不像我已经拥有的那样在目录中创建额外的.py或文件。在虚拟环境中安装包后可以运行测试,就像在 readthedocs 服务器上实现一样。.shbuild.py

我也很熟悉taskipy,所以我的一些 bash 命令pyproject.toml也可以。欢迎使用任何其他软件包pyproject.toml

也许 Poetry 有任何钩子,因为它在创建.whl分发文件时 cythonizes 和 comiles。

对此的任何帮助将不胜感激。

UPD Tox 看起来像是合适的工具,但它pyproject.toml在目录中时看不到。非常欢迎在包或教程中链接到带有 tox 和 cython 的 repos。

4

2 回答 2

2

如果扩展是发行版的一部分,除了运行之外你不需要做任何事情poetry install——poetry将就地构建扩展作为项目可编辑安装的一部分。

在其他情况下,您可以在测试中嵌入调用distutils命令作为套件设置/拆卸的一部分。我不是很熟悉nose,但这里有一个简单的例子。想象一下我有一个fib.pyx(这是 Cython 书中的一个例子):

def fib(long n):
    '''Returns the nth Fibonacci number.'''
    cdef long a=0, b=1, i
    for i in range(n):
        a, b = a + b, a
    return a

test_fib.py构建fib库并在测试成功时将其删除的模块:

from distutils.dist import Distribution
from distutils.core import Extension
from pathlib import Path
from Cython.Build import cythonize


fib_source = Path('fib.pyx')

# distutils magic. This is essentially the same as calling
# python setup.py build_ext --inplace
dist = Distribution(attrs={'ext_modules': cythonize(fib_source.name)})
build_ext_cmd = dist.get_command_obj('build_ext')
build_ext_cmd.ensure_finalized()
build_ext_cmd.inplace = 1
build_ext_cmd.run()

fib_obj = Path(build_ext_cmd.get_ext_fullpath(fib_source.stem))

# the lib was built, so the import will succeed now
from fib import fib


def teardown_module():
    # remove built library
    fib_obj.unlink()

    # if you also want to clean the build dir:
    from distutils.dir_util import remove_tree
    remove_tree(build_ext_cmd.build_lib)
    remove_tree(build_ext_cmd.build_temp)


# sample tests

def test_zero():
    assert fib(0) == 0


def test_ten():
    assert fib(10) == 55

您可能正在自setup_kwargs定义 custom 中的build.py. 要重用此代码,请调整dist初始化,例如:

from build import build

setup_kwargs = {}
build(setup_kwargs)
dist = Distribution(attrs=setup_kwargs)
...

pytest例子

使用 可以更方便地组织事情pytest。创建一个以conftest.py提取到挂钩的安装/拆卸代码命名的文件:

# conftest.py

from distutils.core import Extension
from distutils.dist import Distribution
from distutils.dir_util import remove_tree
from pathlib import Path
from Cython.Build import cythonize


def pytest_sessionstart(session):
    fib_source = Path('fib.pyx')
    dist = Distribution(attrs={'ext_modules': cythonize(fib_source.name)})
    build_ext_cmd = dist.get_command_obj('build_ext')
    build_ext_cmd.ensure_finalized()
    build_ext_cmd.inplace = 1
    build_ext_cmd.run()
    session.fib_obj = Path(build_ext_cmd.get_ext_fullpath(fib_source.stem))


def pytest_sessionfinish(session):
    session.fib_obj.unlink()

现在测试变得更加清晰,并且设置代码在整个测试会话中运行一次。上面的测试例子,重温:

from fib import fib


def test_zero():
    assert fib(0) == 0


def test_ten():
    assert fib(10) == 55
于 2020-03-04T11:33:35.297 回答
0

以下是对已接受答案(pytest 示例)的轻微改动,现在在将 src 代码和测试代码分隔在不同目录中时也可以使用。

例如

.
|_ src/
    |_ your_package/
        |_ some_cython_module.pyx
|_ test/
    |_ conftest.py
    |_ some_cython_module_test.py
# conftest.py

from distutils.dist import Distribution
from pathlib import Path

from Cython.Build import cythonize


def pytest_sessionstart(session):
    difflib_source = Path('src/your_package/some_cython_module.pyx')
    dist = Distribution(attrs={'ext_modules': cythonize(str(difflib_source))})
    build_ext_cmd = dist.get_command_obj('build_ext')
    build_ext_cmd.ensure_finalized()
    build_ext_cmd.inplace = 1
    build_ext_cmd.run()
    session.fib_obj = Path(build_ext_cmd.get_ext_fullpath(
        str(difflib_source.parents[0]) + '/' + difflib_source.stem
    ))


def pytest_sessionfinish(session):
    session.fib_obj.unlink()

于 2020-07-09T14:21:14.097 回答