如何对装饰功能进行单元测试?

ere*_*eOn 12 python unit-testing

我最近尝试过对单元测试最佳实践进行大量训练.其中大部分内容都非常有意义,但有一些内容经常被忽视和/或解释得很糟糕:一个单元测试如何装饰功能?

我们假设我有这个代码:

def stringify(func):
    @wraps(func)
    def wrapper(*args):
        return str(func(*args))

    return wrapper


class A(object):
    @stringify
    def add_numbers(self, a, b):
        """
        Returns the sum of `a` and `b` as a string.
        """
        return a + b
Run Code Online (Sandbox Code Playgroud)

我显然可以写下面的测试:

def test_stringify():
    @stringify
    def func(x):
        return x

    assert func(42) == "42"

def test_A_add_numbers():
    instance = MagicMock(spec=A)
    result = A.add_numbers.__wrapped__(instance, 3, 7)
    assert result == 10
Run Code Online (Sandbox Code Playgroud)

这给了我100%的覆盖率:我知道任何被装饰的函数都stringify()将其结果作为字符串获取,并且我知道未修饰的A.add_numbers()函数返回其参数的总和.因此,通过传递性,装饰版本A.add_numbers()必须返回其参数的总和,作为字符串.一切都好看!

但是我对此并不完全满意:我的测试,因为我写的它们仍然可以通过,如果我要使用另一个装饰器(做其他事情,比如将结果乘以2而不是转换为a str).我的功能A.add_numbers不再正确,但测试仍然会通过.不太棒.

我可以测试装饰版本,A.add_numbers()但随后我会推翻,因为我的装饰器已经过单元测试.

感觉我在这里遗漏了一些东西.单元测试装饰功能的好策略是什么?

ere*_*eOn 7

我最终将我的装饰者一分为二。所以,而不是:

def stringify(func):
    @wraps(func)
    def wrapper(*args):
        return str(func(*args))

    return wrapper
Run Code Online (Sandbox Code Playgroud)

我有:

def to_string(value):
    return str(value)

def stringify(func):
    @wraps(func)
    def wrapper(*args):
        return to_string(func(*args))

    return wrapper
Run Code Online (Sandbox Code Playgroud)

这允许我稍后to_string在测试装饰函数时简单地模拟。

显然,在这个简单的示例案例中,它可能看起来有点矫枉过正,但是当在装饰器上使用时,它实际上做了一些复杂或昂贵的事情(比如打开与数据库的连接,或者其他什么),能够模拟它是一件非常好的事情。