Unit testing py.test测试Cython C API模块
我正在尝试为Cython模块设置单元测试,以测试一些没有python接口的函数。第一个想法是检查Unit testing py.test测试Cython C API模块,unit-testing,cython,pytest,Unit Testing,Cython,Pytest,我正在尝试为Cython模块设置单元测试,以测试一些没有python接口的函数。第一个想法是检查.pyx文件是否可以直接由py.test的测试运行程序使用,但显然它只扫描.py文件 第二个想法是在一个Cython模块中编写test.*方法,然后将其导入到一个普通的.py文件中。假设我们有一个foo.pyx模块,其中包含我们要测试的内容: cdef int is_true(): return False 然后是一个test\u foo.pyx模块,它使用C API测试foo模块: cim
.pyx
文件是否可以直接由py.test
的测试运行程序使用,但显然它只扫描.py
文件
第二个想法是在一个Cython模块中编写test.*
方法,然后将其导入到一个普通的.py
文件中。假设我们有一个foo.pyx
模块,其中包含我们要测试的内容:
cdef int is_true():
return False
然后是一个test\u foo.pyx
模块,它使用C API测试foo
模块:
cimport foo
def test_foo():
assert foo.is_true()
然后将它们导入一个普通的cython_test.py
模块,该模块只包含以下行:
from foo_test import *
py.test
测试运行程序确实以这种方式找到了test\u foo
,但随后报告:
/usr/lib/python2.7/inspect.py:752: in getargs
raise TypeError('{!r} is not a code object'.format(co))
E TypeError: <built-in function test_foo> is not a code object
/usr/lib/python2.7/inspect.py:752:in-getargs
raise TypeError(“{!r}不是代码对象”。格式(co))
E TypeError:不是代码对象
有没有更好的方法可以使用
py.test
测试Cython C-API代码?因此,最后,我设法让py.test
直接从Cython编译的.pyx
文件运行测试。然而,这种方法是一种可怕的黑客行为,旨在尽可能多地利用Python测试运行程序py.test
Python。它可能会停止使用任何与我准备的hack(2.7.2)不同的py.test
版本
第一件事是击败py.test
对.py
文件的关注。最初,py.test
拒绝导入任何没有扩展名为.py
的文件。另一个问题是py.test
验证模块的\uuuuuuuuuu文件是否与.py
文件的位置匹配。Cython的\uuuuu文件\uuuuu
通常不包含源文件的名称。我不得不取消这张支票。我不知道这个覆盖是否会破坏任何东西,我能说的就是测试似乎运行良好,但是如果您担心,请咨询您当地的py.test
开发人员。此部分作为本地conftest.py
文件实现
import _pytest
import importlib
class Module(_pytest.python.Module):
# Source: http://stackoverflow.com/questions/32250450/
def _importtestmodule(self):
# Copy-paste from py.test, edited to avoid throwing ImportMismatchError.
# Defensive programming in py.test tries to ensure the module's __file__
# matches the location of the source code. Cython's __file__ is
# different.
# https://github.com/pytest-dev/pytest/blob/2.7.2/_pytest/python.py#L485
path = self.fspath
pypkgpath = path.pypkgpath()
modname = '.'.join(
[pypkgpath.basename] +
path.new(ext='').relto(pypkgpath).split(path.sep))
mod = importlib.import_module(modname)
self.config.pluginmanager.consider_module(mod)
return mod
def collect(self):
# Defeat defensive programming.
# https://github.com/pytest-dev/pytest/blob/2.7.2/_pytest/python.py#L286
assert self.name.endswith('.pyx')
self.name = self.name[:-1]
return super(Module, self).collect()
def pytest_collect_file(parent, path):
# py.test by default limits all test discovery to .py files.
# I should probably have introduced a new setting for .pyx paths to match,
# for simplicity I am hard-coding a single path.
if path.fnmatch('*_test.pyx'):
return Module(path, parent)
第二个主要问题是py.test
使用Python的inspect
模块检查单元测试的函数参数名称。记住,py.test
这样做是为了注入fixture,这是一个非常漂亮的特性,值得保留inspect
不适用于Cython,一般来说,似乎没有简单的方法使originalinspect
适用于Cython。也没有其他好的方法来检查Cython函数的参数列表。现在,我决定做一个小的变通方法,将所有测试函数包装在一个带有所需签名的纯Python函数中
除此之外,Cython似乎会自动为每个.pyx
模块添加一个\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
属性。Cython的做法会干扰py.test,需要修复。据我所知,\uuuuu test\uuuu
是Cython的一个内部细节,没有在任何地方公开,所以我们覆盖它并不重要。在我的例子中,我将以下函数放入.pxi
文件中,以包含在任何*\u test.pyx
文件中:
from functools import wraps
# For https://github.com/pytest-dev/pytest/blob/2.7.2/_pytest/python.py#L340
# Apparently Cython injects its own __test__ attribute that's {} by default.
# bool({}) == False, and py.test thinks the developer doesn't want to run
# tests from this module.
__test__ = True
def cython_test(signature=""):
''' Wrap a Cython test function in a pure Python call, so that py.test
can inspect its argument list and run the test properly.
Source: http://stackoverflow.com/questions/32250450/'''
if isinstance(signature, basestring):
code = "lambda {signature}: func({signature})".format(
signature=signature)
def decorator(func):
return wraps(func)(eval(code, {'func': func}, {}))
return decorator
# case when cython_test was used as a decorator directly, getting
# a function passed as `signature`
return cython_test()(signature)
之后,我可以执行以下测试:
include "cython_test_helpers.pxi"
from pytest import fixture
cdef returns_true():
return False
@cython_test
def test_returns_true():
assert returns_true() == True
@fixture
def fixture_of_true():
return True
@cython_test('fixture_of_true')
def test_fixture(fixture_of_true):
return fixture_of_true == True
如果您决定使用上述黑客,请记住给自己留下一条评论,并附上此答案的链接——我会尽力更新它,以备有更好的解决方案可用。简单说明一下:我找到了一种黑客方法,但是我需要一些时间来记录它。请看一下,了解如何编写一个可以直接从.pyx
文件运行测试的插件。@BrunoOliveira:您可能对我在下面发布的解决方案感兴趣。Cython编译的模块与纯Python模块没有太大区别,因此我决定采用一种短期的破解方法,我希望在Python的未来版本中不必进行轻微调整(以便inspect.getargspec
使用用户定义的类型),Cython(实现inspect.getargspec
并去掉它的内部\uuuuuu测试\uuuuuuu
属性)以及py.test(调整一些断言)。