2015-02-24 3 views
10

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

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 
    ... 

ответ

11

Вместо того, чтобы издеваться значение, если 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, поэтому он должен тайм-аут, когда после нескольких оборотов вашего цикла. Вы также можете высмеять time.sleep и просто подсчитать, сколько раз он вызывается, чтобы убедиться, что часть кода работает правильно.


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

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

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

+0

mock_time это! – anregen

+0

На самом деле я использовал '@ patch.object (myClass.time, 'time')', чтобы локальный mock.side_effect был локальным для этого модуля. – anregen

11

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

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

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

+0

И я просто редактировал это в свой ответ ... :-) – mgilson

+3

Исходная функция является частью опубликованного API, поэтому я не хочу менять подпись, и я не хочу, чтобы пользователи имели доступ к отрегулируйте этот тайм-аут. – anregen

+3

Python работает как соглашение джентльмена и некоторые соглашения - если вы сделаете аргумент единственным лидирующим подчеркиванием - ваши пользователи будут знать, что аргумент не должен использоваться, - и любая достойная система документации позволит вам опустить частные аргументы, методы, поля и т. д., так что только очень определенный пользователь найдет аргумент в первую очередь. –

Смежные вопросы