简体   繁体   English

模拟副作用只有 X 次

[英]Mock side effect only X number of times

I have a celery retry task that I would like to test that it retries until successful.我有一个芹菜重试任务,我想测试它是否重试直到成功。 Using mock's side_effect, I can fail it for a set number of executions and then passing None , clear the side effect.使用 mock 的 side_effect,我可以在一定数量的执行中失败,然后通过None ,清除副作用。 However, the method the task is calling doesn't execute at that point, it just doesn't have an exception.但是,任务调用的方法在那个时候不会执行,它只是没有异常。 Is there a way to clear the side effect, and still have the method being mocked execute as normal?有没有办法清除副作用,并且仍然让被模拟的方法正常执行?

I can test that it is called 'x' number of times (ie. repeat until successful) and then in a separate test, assert it does what is supposed to, but was wondering if there was a way to do both in one test.我可以测试它被称为 'x' 次(即重复直到成功),然后在单独的测试中,断言它做了应该做的事情,但想知道是否有办法在一个测试中做到这两点。

tasks.py:任务.py:

import celery

@celery.task(max_retries=None)
def task():
    print "HERE"
    try:
        do_something("TASK")
    except Exception as exc:
        print exc
        raise task.retry(exc=exc)

def do_something(msg):
    print msg

Test:测试:

import ....

class TaskTests(test.TestCase):

    @mock.patch('tasks.do_something')
    def test_will_retry_until_successful(self, action):
        action.side_effect = [Exception("First"), Exception("Second"), Exception("Third"), None]
        tasks.task.delay()
        self.assert.... [stuff about task]

Results: fails three times and then "succeeds" but do_something() never prints.结果:失败 3 次,然后“成功”但do_something()从不打印。 action.call_count equals 4. I would like to see that the blank line following the last 'HERE' would be print of 'TASK'. action.call_count等于 4。我希望看到最后一个“HERE”后面的空行将打印“TASK”。

-------------------- >> begin captured stdout << ---------------------
HERE
First
HERE
Second
HERE
Third
HERE

--------------------- >> end captured stdout << ----------------------

You mocked do_something() . 嘲笑了do_something() A mock replaces the original entirely; 模拟完全替换原文; your choices are to either have the side effect (raise or return a value from the iterable) or to have the normal mock operations apply (returning a new mock object). 您的选择是要么具有副作用(从迭代中提高或返回值),要么应用正常的模拟操作(返回一个新的模拟对象)。

In addition, adding None to the side_effect sequence doesn't reset the side effect, it merely instructs the mock to return the value None instead. 另外,在side_effect序列中添加None不会重置副作用,它只是指示mock返回值None You could add in mock.DEFAULT instead; 你可以添加mock.DEFAULT而不是; in that case the normal mock actions apply (as if the mock had been called without a side effect): 在这种情况下,正常的模拟操作适用(就好像模拟被调用而没有副作用):

@mock.patch('tasks.do_something')
def test_will_retry_until_successful(self, action):
    action.side_effect = [Exception("First"), Exception("Second"), Exception("Third"), mock.DEFAULT]
    tasks.task.delay()
    self.assert.... [stuff about task]

If you feel your test must end with calling the original, you'll have to store a reference to the original, unpatched function, then set the side_effect to a callable that will turn around and call the original when the time comes: 如果您认为测试必须以调用原始函数结束,则必须存储对原始未修补函数的引用,然后将side_effect设置为可调用的可调用函数,并在时间到来时调用原始函数:

# reference to original, global to the test module that won't be patched
from tasks import do_something

class TaskTests(test.TestCase):
    @mock.patch('tasks.do_something')
    def test_will_retry_until_successful(self, action):
        exceptions = iter([Exception("First"), Exception("Second"), Exception("Third")])
        def side_effect(*args, **kwargs):
            try:
                raise next(exceptions)
            except StopIteration:
                # raised all exceptions, call original
                return do_something(*args, **kwargs)
        action.side_effect = side_effect
        tasks.task.delay()
        self.assert.... [stuff about task]

I cannot, however, foresee a unittesting scenario where you'd want to do that. 但是,我不能预见到你想要这样做的单元测试场景。 do_something() is not part of the Celery task being tested, it is an external unit, so you should normally only test if it was called correctly (with the right arguments), and the correct number of times. do_something()不是正在测试的Celery任务的一部分,它是一个外部单元,因此通常只应测试它是否被正确调用(使用正确的参数),以及正确的次数。

In case someone just needs to return values after the stopIterations happens, you just need to return the iterator, not the exception.如果有人只需要在 stopIterations 发生后返回值,您只需要返回迭代器,而不是异常。

responses = [1,2,3,4,5]

def requests_side_effect(*args, **kwargs):
    try:
        return next(responses)
    except StopIteration:
        # raised all exceptions, call original
        return default_request_mock

This will retrieve values for the first 5 calls and then return the default value.这将检索前 5 次调用的值,然后返回默认值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM