假设我有一个用assert
-statements表示为简单脚本的测试(请参阅背景了解原因),例如
import foo
assert foo(3) == 4
我如何以一种不错的方式将该脚本包含在pytest测试套件中?
我尝试了两种可行但不那么精妙的方法:
一种方法是像测试一样命名脚本,但这会使整个pytest发现在测试失败时失败。
我当前的方法是从测试函数中导入脚本:
def test_notebooks():
notebook_folder = Path(__file__).parent / 'notebooks'
for notebook in notebook_folder.glob('*.py'):
import_module(f'{notebook_folder.name}.{notebook.stem}')
这可以工作,但是不会单独报告脚本,并且测试失败的堆栈跟踪时间长且蜿蜒:
__________________________________________________ test_notebooks ___________________________________________________
def test_notebooks():
notebook_folder = Path(__file__).parent / 'notebooks'
for notebook in notebook_folder.glob('*.py'):
> import_module(f'{notebook_folder.name}.{notebook.stem}')
test_notebooks.py:7:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
envs\anaconda\lib\importlib\__init__.py:127: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
<frozen importlib._bootstrap>:1006: in _gcd_import
... (9 lines removed)...
<frozen importlib._bootstrap>:219: in _call_with_frames_removed
???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> assert False
E AssertionError
notebooks\notebook_2.py:1: AssertionError
我在脚本文件中进行测试的原因是,它们实际上是Jupyter笔记本.py
,由出色的jupytext插件另存为带有标记的-files。
这些笔记本将转换为html进行文档编制,可以交互方式用于学习系统,并用作廉价的功能测试。
从测试函数调用脚本没有错,因此您的方法非常好。但是,我将使用参数化,而不是在for循环中运行脚本。这样,您可以使每个脚本很好地执行一次测试。如果您不喜欢冗长的回溯,则可以在自定义的pytest_exception_interact
hookimpl中进行裁剪。例:
# conftest.py
def pytest_exception_interact(node, call, report):
excinfo = call.excinfo
if 'script' in node.funcargs:
excinfo.traceback = excinfo.traceback.cut(path=node.funcargs['script'])
report.longrepr = node.repr_failure(excinfo)
参数化测试:
# test_spam.py
import pathlib
import runpy
import pytest
scripts = pathlib.Path(__file__, '..', 'scripts').resolve().glob('*.py')
@pytest.mark.parametrize('script', scripts)
def test_script_execution(script):
runpy.run_path(script)
测试执行收益率(为了进行测试,我使用诸如assert False
或的单行创建了简单的脚本1 / 0
:
$ pytest -v
======================================= test session starts ========================================
platform linux -- Python 3.6.8, pytest-4.6.3, py-1.8.0, pluggy-0.12.0 -- /home/hoefling/projects/.venvs/stackoverflow/bin/python3.6
cachedir: .pytest_cache
rootdir: /home/hoefling/projects/private/stackoverflow/so-56807698
plugins: mock-1.10.4, cov-2.7.1, forked-1.0.2, xdist-1.28.0, django-3.4.8
collected 3 items
test_spam.py::test_script_execution[script0] PASSED
test_spam.py::test_script_execution[script1] FAILED
test_spam.py::test_script_execution[script2] FAILED
============================================= FAILURES =============================================
____________________________________ test_script_runpy[script1] ____________________________________
> assert False
E AssertionError
scripts/script_3.py:1: AssertionError
____________________________________ test_script_runpy[script2] ____________________________________
> 1 / 0
E ZeroDivisionError: division by zero
scripts/script_2.py:1: ZeroDivisionError
================================ 2 failed, 1 passed in 0.07 seconds ================================
如果您不喜欢上述解决方案,那么我想到的另一件事是实现自己的测试收集和执行协议。例:
# conftest.py
import pathlib
import runpy
import pytest
def pytest_collect_file(parent, path):
p = pathlib.Path(str(path))
if p.suffix == '.py' and p.parent.name == 'scripts':
return Script(path, parent)
class Script(pytest.File):
def collect(self):
yield ScriptItem(self.name, self)
class ScriptItem(pytest.Item):
def runtest(self):
runpy.run_path(self.fspath)
def repr_failure(self, excinfo):
excinfo.traceback = excinfo.traceback.cut(path=self.fspath)
return super().repr_failure(excinfo)
这将收集目录中的每个.py
文件scripts
,将每个脚本包装在测试用例中,并runpy
在测试执行时调用。执行日志看起来几乎相同,只是测试的名称不同。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句