如何在同一测试用例中使用假设和基于 pytest-tornado 产量的测试?

How can I use hypothesis, and pytest-tornado yield-based testing, in the same test case?

我正在写 py.test tests of my code that uses the Tornado library. How can I use Hypothesis in my tests that involve coroutines and the IOLoop? I've been able to write yield-based tests without Hypothesis by using pytest-tornado@pytest.mark.gen_test,但是当我尝试将它与 @given 组合时,我收到以下错误:

FailedHealthCheck: Tests run under @given should return None, but test_both returned <generator object test_both at 0x7fc4464525f0> instead.

See http://hypothesis.readthedocs.org/en/latest/healthchecks.html for more information about this. If you want to disable just this health check, add HealthCheck.return_value to the suppress_health_check settings for this test.

考虑到 Hypothesis docs

,我非常有信心这是一个真正的问题,而不仅仅是禁用健康检查的问题

yield based tests simply won’t work.

这是演示我的情况的代码:

class MyHandler(RequestHandler):

    @gen.coroutine
    def get(self, x):
        yield gen.moment
        self.write(str(int(x) + 1))
        self.finish()


@pytest.fixture
def app():
    return Application([(r'/([0-9]+)', MyHandler)])


@given(x=strategies.integers(min_value=0))
def test_hypothesis(x):
    assert int(str(x)) == x


@pytest.mark.gen_test
def test_tornado(app, http_client, base_url):
    x = 123
    response = yield http_client.fetch('%s/%i' % (base_url, x))
    assert int(response.body) == x + 1


@pytest.mark.gen_test
@given(x=strategies.integers(min_value=0))
def test_both(x, app, http_client, base_url):
    response = yield http_client.fetch('%s/%i' % (base_url, x))
    assert int(response.body) == x + 1

test_hypothesistest_tornado 工作正常,但我得到 test_both 的错误,因为我同时使用 yield 和假设。

改变装饰器的顺序并没有改变任何东西,可能是因为 gen_test 装饰器只是一个属性标记。

我可以编写使用假设的基于 Tornado 的代码的测试吗?怎么样?

您可以通过在 pytest-tornado 的 io_loop py.test 夹具上调用 run_sync() 来完成此操作。这可以用来代替 yield:

@given(x=strategies.integers(min_value=0))
def test_solution(x, app, http_client, base_url, io_loop):
    response = io_loop.run_sync(
        lambda: http_client.fetch('%s/%i' % (base_url, x)))
    assert int(response.body) == x + 1

或者你可以把你的测试主体放在协程中,这样它就可以继续使用yield,并用run_sync()调用这个协程:

@given(x=strategies.integers(min_value=0))
def test_solution_general(x, app, http_client, base_url, io_loop):
    @gen.coroutine
    def test_gen():
        response = yield http_client.fetch('%s/%i' % (base_url, x))
        assert int(response.body) == x + 1
    io_loop.run_sync(test_gen)