Переопределить функцию python-local в unittest

У меня есть метод в python (2.7), который делает foo и сбрасывается через 5 минут, если foo не работает.

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')

Я знаю некоторые возможные результаты от 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
    ...

Ответ 1

Вместо того, чтобы пытаться высмеять значение, если 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])
    ...

Теперь, когда вызывается первый раз time.time(), вы получите значение 100, поэтому он должен истечь, если после нескольких оборотов вашего цикла while. Вы также можете высмеять time.sleep и просто подсчитать, сколько раз оно вызывается, чтобы убедиться, что часть кода работает правильно.


Другой подход (который не является полностью ортогональным выше) заключается в том, чтобы позволить пользователю передать необязательное ключевое слово timeout для функции:

def keep_trying(self, timeout=300):
    ...

Это позволяет вам указать любой тайм-аут, который вы хотите в тестах (и в будущем коде, который не хочет ждать 5 минут; -).

Ответ 2

Вы не можете издеваться над локальной переменной функции. Чтобы ваш код был легче протестирован, измените его на, например:

def keep_trying(self, timeout=300):
    end_time = time.time() + timeout
    # etc, as above

поэтому для тестов запускать его с меньшим таймаутом становится тривиальным!