在unittest中覆盖python函数局部变量

anr*_*gen 19 python unit-testing mocking

我在python(2.7)中有一个foo方法,如果foo不起作用,在5分钟后放弃.

def keep_trying(self):
    timeout = 300  #empirically derived, appropriate timeout
    end_time = time.time() + timeout
    while (time.time() < end_time):
        result = self.foo()
        if (result == 'success'):
            break
        time.sleep(2)
    else:
        raise MyException('useful msg here')
Run Code Online (Sandbox Code Playgroud)

我知道foo()的一些可能的结果,所以我使用mock来伪造那些返回值.问题是,我不希望测试在看到异常之前运行5分钟.

有没有办法覆盖超时的本地值?我希望这只是几秒钟,以便我可以看到循环尝试几次,然后放弃并加注.

以下不起作用:

@patch.object(myClass.keep_trying, 'timeout')
@patch.object(myClass, 'foo')
def test_keep_trying(self, mock_foo, mock_timeout):
    mock_foo.return_value = 'failed'
    mock_timeout.return_value = 10 # raises AttributeError
    mock_timeout = 10 # raises AttributeError
    ...
Run Code Online (Sandbox Code Playgroud)

Ale*_*lli 17

你不能模拟一个函数的局部变量.为了使代码更容易测试,请将其更改为,例如:

def keep_trying(self, timeout=300):
    end_time = time.time() + timeout
    # etc, as above
Run Code Online (Sandbox Code Playgroud)

因此,测试以较短的超时运行它变得微不足道!

  • 原始函数是已发布API的一部分,因此我不想更改签名,并且我不希望用户有权调整该超时. (3认同)
  • Python作为一个绅士的协议和某些约定 - 如果你使论证有一个前导下划线 - 你的用户将知道不应该使用该参数 - 任何体面的文档系统将允许你省略私有参数,方法,字段等无论如何,所以只有非常坚定的用户才会首先找到参数. (3认同)

mgi*_*son 16

而不是试图模拟值,如果timeout,你将要模拟的返回值time.time().

例如

@patch.object(time, 'time')
def test_keep_trying(self, mock_time):
    mock_time.side_effect = iter([100, 200, 300, 400, 500, 600, 700, 800])
    ...
Run Code Online (Sandbox Code Playgroud)

现在第一次time.time()调用时,你将得到100的值,所以它会在你的while循环几圈之后超时.您还可以模拟time.sleep并计算调用它的次数,以确保部分代码正常工作.


另一种方法(与上面的方法不完全正交)是允许用户将可选的timeout关键字传递给函数:

def keep_trying(self, timeout=300):
    ...
Run Code Online (Sandbox Code Playgroud)

这允许您在测试中指定您想要的任何超时(以及将来不想等待5分钟的代码;-).