Почему я получаю ошибку рекурсии: максимальная глубина рекурсии превышена при издевательстве над датой и временем.поведение на свидании?

#python-3.x #unit-testing #python-import #python-unittest

Вопрос:

Я издеваюсь над следующей функцией в модуле sample.py , следуя примеру из unittest docs:

 import datetime
from random import randint

def useless_date():
    today_date = datetime.date.today()
    x = randint(0,1000)
    dt = datetime.timedelta(days = x)

    return today_date   dt
 

Я настроил файл test_file и заметил некоторое странное поведение в python, которое я не понимаю. Тесты выполняются идеально, если используются явные date и timedelta импортные:

 from unittest import mock
from sample import useless_date
from datetime import date, timedelta

@mock.patch("sample.datetime.date")
@mock.patch("sample.datetime.timedelta")
def test_useless_date(mock_timedelta, mock_date):
    mock_date.today.return_value = date(1999, 1, 1)
    mock_timedelta.return_value = timedelta(days=1)
    mock_date.side_effect = lambda *args, **kwargs: date(*args, **kwargs)

    assert useless_date() == date(1999, 1, 2)
 

Однако, если я просто импортирую datetime модуль без явного импорта date , и timedelta я получу ошибку рекурсии.

Код:

 from unittest import mock
from sample import useless_date
import datetime

@mock.patch("sample.datetime.date")
@mock.patch("sample.datetime.timedelta")
def test_useless_date(mock_timedelta, mock_date):
    date = datetime.date
    timedelta = datetime.timedelta
    mock_date.today.return_value = date(1999, 1, 1)
    mock_timedelta.return_value = timedelta(days=1)
    mock_date.side_effect = lambda *args, **kwargs: date(*args, **kwargs)

    assert useless_date() == date(1999, 1, 2)
 

Ошибка:

 __________________________________________________________________________________________________________ test_useless_date ___________________________________________________________________________________________________________

mock_timedelta = <MagicMock name='timedelta' id='4466974048'>, mock_date = <MagicMock name='date' id='4467058096'>

    @mock.patch("sample.datetime.date")
    @mock.patch("sample.datetime.timedelta")
    def test_useless_date(mock_timedelta, mock_date):
        date = datetime.date
        timedelta = datetime.timedelta
        mock_date.today.return_value = date(1999, 1, 1)
        mock_timedelta.return_value = timedelta(days=1)
        mock_date.side_effect = lambda *args, **kwargs: date(*args, **kwargs)
    
>       assert useless_date() == date(1999, 1, 2)

test_sample.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1081: in __call__
    return self._mock_call(*args, **kwargs)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1085: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1146: in _execute_mock_call
    result = effect(*args, **kwargs)
test_sample.py:58: in <lambda>
    mock_date.side_effect = lambda *args, **kwargs: date(*args, **kwargs)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1081: in __call__
    return self._mock_call(*args, **kwargs)
E   RecursionError: maximum recursion depth exceeded
!!! Recursion detected (same locals amp; position)
======================================================================================================= short test summary info ========================================================================================================
FAILED test_sample.py::test_useless_date - RecursionError: maximum recursion depth exceeded


 

Версия Python: Python 3.8.2

Ответ №1:

Рекурсия в вашем тесте происходит потому, что при написании date = datetime.date вы получаете не то, что думаете (исходную datetime.date функцию), а функцию, над которой издеваются.

Как import datetime и в тестируемой функции, используемый модуль является глобальным модулем, поэтому запись @mock.patch("sample.datetime.date") имеет тот же эффект, что и запись @mock.patch("datetime.date") — вы исправляете глобальную datetime.date функцию. В вашем коде это означает , что date это то же mock_date самое, что и, и ваш побочный эффект такой же, как при написании

 mock_date.side_effect = lambda *args, **kwargs: mock_date(*args, **kwargs)
 

Это, очевидно, вызывает рекурсию.

Этого не произойдет, если вы используете from datetime import date , потому что в этом случае date появляется новая ссылка на datetime.date проживание в модуле, откуда она была импортирована. Таким образом, в вашей первой версии date переменная ссылается на test_sample.date (при условии test_sample , что это имя тестового модуля), а не на datetime.date . То же самое произошло бы, если бы вы использовали from datetime import date sample.py . В этом случае вам придется исправлять sample.date , что не то же самое datetime.date , и ваш второй тест также будет работать.

Комментарии:

1. Спасибо, что объяснили это!