使用奇怪的语法用 pytest 测试装饰器

testing decorator with pytest using weird syntax

所以我想测试一个装饰器,它可以为您提供一个准备就绪的协程(所以我不必这样做 coroutine.send(None))。

可以找到详细信息here。大卫·比兹利的例子)

所以在 conftest.py 中,我创建了一个函数包装器,它是一个 pytest 夹具,如果有意义的话,它会给我一个原始协程。

我这样做的原因是因为 pytest 会将 yield 关键字之后的任何内容作为拆卸代码,而我不希望这样。所以我自己包装了一个原始协程。

@pytest.fixture(scope="module")
def create_coroutine_func():

    def grep(pattern):
        print "Looking for %s" % pattern
        while True:
            line = (yield)
            if pattern in line:
                print line
    return grep

现在在我的测试文件中我有一个相当简单的测试:

    from something.decorators import *
    import pytest

1    def test_coroutine(create_coroutine_func):
2        coro_func = create_coroutine_func()
3        coro_wrapper = coroutine(coro_func)
4    
5        assert callable(coro_wrapper)

好的,就是这样。 pytest 抱怨:

create_coroutine() missing 1 required positional argument: 'pattern'

然后我继续编辑第 2 行:

coro_func = create_coroutine_func("python") # match anything has 'python' in it.

现已通过测试。

现在我对装饰器有点困惑了。

因此在我的示例中,grep 需要一个参数;当我调用我的 grep 函数包装器时,我还需要给函数包装器一个参数,尽管它本身不接受任何参数?装饰器是这样工作的吗?

UPD:正如@jacques-kvam 在评论中指出的那样,当您将夹具作为参数传递给测试函数时,夹具的 return 值就是参数的值。所以在你的例子中,第 2 行 create_coroutine_funcgrep.

这种对 pytest fixtures 的误解可能是误用测试函数本身的值的主要原因。

弄清楚后,这是我的原始答案:


在这里,您 create_coroutine_func() 的第 2 行调用 您的函数。这就是它希望传递参数的原因。

如果您想将函数包装到装饰器中,您实际需要的是对函数对象本身进行操作。它不调用它,而是将它作为值传递给装饰器:

def test_coroutine(create_coroutine_func):
    coro_wrapper = coroutine(create_coroutine_func)
    assert callable(coro_wrapper)
    coro_wrapper("python")

装饰器是这样工作的:它们将一个函数作为参数,return 另一个(或相同的)函数作为结果。


PS:同样的测试可以用更好的语法编写:

from something.decorators import *
import pytest

def test_coroutine(create_coroutine_func):

    @coroutine
    def grep(pattern):
        print "Looking for %s" % pattern
        while True:
            line = (yield)
            if pattern in line:
                print line       

    assert callable(grep)
    grep("python")

注意装饰器的使用在测试中。因此,如果出现任何问题,测试将正常失败,而不是 pytest 的模块导入(如果修饰函数是在模块级别声明的,您可能已经注意到了,就会发生这种情况)。