“ pytest”和“基于屈服”的测试



我正在尝试将一堆测试从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_simplenosepytest之间运行良好,但是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个额外的yields。

有什么办法可以在pytest中复制此测试结构?通过pytest_generate_tests

此测试代表了更大的事件序列,以在过程的每个阶段构建对象并在其上进行操作。

  1. 模型
  2. 验证某些模型属性
  3. 基于模型创建和输出文件
  4. 差异针对已知输出,以查看是否存在变化。

预先感谢

由于测试的输出建议,基于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

我建议您改用Decorator pytest.parametrize。您可以在:

上检查更多有关它的信息
  • https://docs.pytest.org/en/latest/how-to/parametrize.html
  • https://docs.pytest.org/en/latest/example/parametrize.html

从您的示例中,我将为测试创建类似的东西:

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函数直到收集过程结束后才调用。

ayou可以通过将is_generator支票从集合阶段移动到测试跑步阶段来进行测试,通过将以下内容放在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

自从版本3.2.1以来,Pytest发生了太大变化,上述可能无法正常工作。相反,请根据适当地复制并修改最新版本的_pytest.main.pytest_runtestloop;这应该为您的项目提供时间逐渐迁移到基于收益的测试案例,或者至少可以在收集时间收集的基于收益的测试用例。

相关内容

  • 没有找到相关文章