[英]`pytest` and `yield`-based tests
我正在尝试将一堆测试从nose
迁移到pytest
,但在迁移一个验证整个过程的测试时遇到了麻烦。
我已经把它简化为代表我的问题:
def is_equal(a, b):
assert a == b
def inner():
yield is_equal, 2, 2
yield is_equal, 3, 3
def test_simple():
yield is_equal, 0, 0
yield is_equal, 1, 1
for test in inner():
yield test
yield is_equal, 4, 4
yield is_equal, 5, 5
def test_complex():
integers = list()
def update_integers():
integers.extend([0, 1, 2, 3, 4, 5])
yield update_integers
for x in integers:
yield is_equal, x, x
test_simple
在nose
和pytest
之间运行良好,但test_complex
只运行初始update_integers
测试:
~/projects/testbox$ nosetests -v
test_nose_tests.test_simple(0, 0) ... ok
test_nose_tests.test_simple(1, 1) ... ok
test_nose_tests.test_simple(2, 2) ... ok
test_nose_tests.test_simple(3, 3) ... ok
test_nose_tests.test_simple(4, 4) ... ok
test_nose_tests.test_simple(5, 5) ... ok
test_nose_tests.test_complex ... ok
test_nose_tests.test_complex(0, 0) ... ok
test_nose_tests.test_complex(1, 1) ... ok
test_nose_tests.test_complex(2, 2) ... ok
test_nose_tests.test_complex(3, 3) ... ok
test_nose_tests.test_complex(4, 4) ... ok
test_nose_tests.test_complex(5, 5) ... ok
----------------------------------------------------------------------
Ran 13 tests in 0.004s
~/projects/testbox$ pytest -v
==================================================================== test session starts =====================================================================
platform linux2 -- Python 2.7.12, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- /usr/bin/python
cachedir: .cache
rootdir: /home/local/ANT/cladam/projects/testbox, inifile:
collected 7 items
tests/test_nose_tests.py::test_simple::[0] PASSED
tests/test_nose_tests.py::test_simple::[1] PASSED
tests/test_nose_tests.py::test_simple::[2] PASSED
tests/test_nose_tests.py::test_simple::[3] PASSED
tests/test_nose_tests.py::test_simple::[4] PASSED
tests/test_nose_tests.py::test_simple::[5] PASSED
tests/test_nose_tests.py::test_complex::[0] PASSED
=================================================================== pytest-warning summary ===================================================================
WC1 /home/local/ANT/cladam/projects/testbox/tests/test_nose_tests.py yield tests are deprecated, and scheduled to be removed in pytest 4.0
....
======================================================== 7 passed, 7 pytest-warnings in 0.01 seconds =========================================================
我假设这是因为在收集时整数列表是空的,然后它不会收集 6 个额外的yield
s。
有什么办法可以在pytest
复制这个测试结构吗? 通过pytest_generate_tests
?
该测试代表了一个更大的事件序列,用于构建对象并对其进行操作,并在过程的每个阶段进行测试。
提前致谢
正如您的测试输出所表明的,基于yield
的测试已被弃用:
WC1 /home/local/ANT/cladam/projects/testbox/tests/test_nose_tests.py yield tests are deprecated, and scheduled to be removed in pytest 4.0
我建议您改用装饰器pytest.parametrize
。 您可以在以下位置查看更多信息:
从你的例子中,我会为测试创建这样的东西:
import pytest
def is_equal(a, b):
return a == b
class TestComplexScenario:
@pytest.mark.parametrize("my_integer", [0, 1, 2])
def test_complex(self, my_integer):
assert is_equal(my_integer, my_integer)
以下是输出示例:
test_complex.py::TestComplexScenario::test_complex[0] PASSED
test_complex.py::TestComplexScenario::test_complex[1] PASSED
test_complex.py::TestComplexScenario::test_complex[2] PASSED
您可以在以下位置找到更多关于参数化的示例: http : //layer0.authentise.com/pytest-and-parametrization.html
您还可以对您的测试输入进行排列,请查看示例: 在 pytest 中使用笛卡尔乘积的参数进行参数化测试
问题是 pytest 在运行任何测试之前收集所有测试,因此在test_complex
,直到收集过程结束后才调用update_integers
函数。
A您可以通过将is_generator
检查从收集阶段移动到测试运行阶段来运行测试,方法是在conftest.py
放置以下conftest.py
。 不幸的是,钩子不允许pytest_runtest_protocol
作为生成器运行,因此 pytest-3.2.1 的_pytest.main.pytest_runtestloop
的全部内容被复制和修改。
import pytest
from _pytest.compat import is_generator
def pytest_pycollect_makeitem(collector, name, obj):
"""
Override the collector so that generators are saved as functions
to be run during the test phase rather than the collection phase.
"""
if collector.istestfunction(obj, name) and is_generator(obj):
return [pytest.Function(name, collector, args=(), callobj=obj)]
def pytest_runtestloop(session):
"""
Copy of _pytest.main.pytest_runtestloop with the session iteration
modified to perform a subitem iteration.
"""
if (session.testsfailed and
not session.config.option.continue_on_collection_errors):
raise session.Interrupted(
"%d errors during collection" % session.testsfailed)
if session.config.option.collectonly:
return True
for i, item in enumerate(session.items):
nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
# The new functionality is here: treat all items as if they
# might have sub-items, and run through them one by one.
for subitem in get_subitems(item):
subitem.config.hook.pytest_runtest_protocol(item=subitem, nextitem=nextitem)
if getattr(session, "shouldfail", False):
raise session.Failed(session.shouldfail)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
def get_subitems(item):
"""
Return a sequence of subitems for the given item. If the item is
not a generator, then just yield the item itself as the sequence.
"""
if not isinstance(item, pytest.Function):
yield item
obj = item.obj
if is_generator(obj):
for number, yielded in enumerate(obj()):
index, call, args = interpret_yielded_test(yielded, number)
test = pytest.Function(item.name+index, item.parent, args=args, callobj=call)
yield test
else:
yield item
def interpret_yielded_test(obj, number):
"""
Process an item yielded from a generator. If the item is named,
then set the index to "['name']", otherwise set it to "[number]".
Return the index, the callable and the arguments to the callable.
"""
if not isinstance(obj, (tuple, list)):
obj = (obj,)
if not callable(obj[0]):
index = "['%s']"%obj[0]
obj = obj[1:]
else:
index = "[%d]"%number
call, args = obj[0], obj[1:]
return index, call, args
如果 pytest 自 3.2.1 版以来更改太多,则上述内容可能不起作用。 相反,根据需要复制和修改最新版本的_pytest.main.pytest_runtestloop
; 这应该为您的项目提供时间逐渐从基于产量的测试用例迁移,或者至少迁移到可以在收集时收集的基于产量的测试用例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.