Издевательство над "с помощью open ()"

Я пытаюсь выполнить модульное тестирование метода, который считывает строки из файла и обрабатывает их.

with open([file_name], 'r') as file_list:
    for line in file_list:
        # Do stuff

Я пробовал несколько способов, описанных в других вопросах, но ни один из них, похоже, не работает в этом случае. Я не совсем понимаю, как python использует объект файла как итерацию по строкам, он внутренне использует file_list.readlines ()?

Этот способ не сработал:

    with mock.patch('[module_name].open') as mocked_open: # also tried with __builtin__ instead of module_name
        mocked_open.return_value = 'line1\nline2'

Я получил

AttributeError: __exit__

Может быть, потому что у оператора with есть этот специальный атрибут для закрытия файла?

Этот код превращает file_list в MagicMock. Как мне хранить данные на этом MagicMock, чтобы перебирать их?

with mock.patch("__builtin__.open", mock.mock_open(read_data="data")) as mock_file:

С наилучшими пожеланиями

Фраза for x in y: создает итератор через вызов iter(y), который попытается вызвать y.__iter__() для его создания. Поэтому я предлагаю перехватить этот звонок с помощью имитации.

Alfe 31.10.2018 14:06
read_dataявляется данные, которые вы перебираете.
chepner 31.10.2018 14:06

Однако это может быть тот случай, когда вы захотите изменить свой код, чтобы упростить тестирование. Предполагая, что это функция, которая принимает имя файла в качестве аргумента, пусть она вместо этого принимает файл объект. Это делает вызывающего пользователя ответственным за открытие файла, но также позволяет вам передать объект StringIO в качестве аргумента для тестирования. (Как правило, продвижение ввода-вывода как можно дальше к краям кода упрощает тестирование.)

chepner 31.10.2018 14:09

@chepner Я думаю, что read_data применяется только к методу file_obj.read (), и этот метод вернул мне read_data. Потому что в этом коде он никогда не входит в цикл for, вероятно, потому, что он пытается перебрать MagicMock, и это не работает.

tanaka 31.10.2018 14:18

@chepner Рефакторинг - очень хороший совет, спасибо!

tanaka 31.10.2018 14:18

@Alfe В этом есть смысл. Однако я понятия не имею, как перехватить и имитировать метод .__ iter __ (), чтобы он мог перебирать некоторые данные в объекте MagicMock. У вас есть какой-нибудь пример или ссылка на это?

tanaka 31.10.2018 14:22

Глядя на документацию для mock_open, похоже, что поддержка __iter__ не была добавлена ​​до Python 3.7. (Что мне кажется оплошностью огромный, но, думаю, лучше поздно, чем никогда.)

chepner 31.10.2018 14:24

Приятно знать (это действительно похоже на недосмотр)

tanaka 31.10.2018 14:31
0
8
156
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Возвращаемое значение mock_open (до Python 3.7.1) не предоставляет работающего метода __iter__, что может сделать его непригодным для тестирования кода, который выполняет итерацию по объекту открытого файла.

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

def some_method(file_name):
    with open([file_name], 'r') as file_list:
        for line in file_list:
            # Do stuff

...

 some_method(file_name)

напишите это как

def some_method(file_obj):
    for line in file_obj:
        # Do stuff

...

with open(file_name, 'r') as file_obj:
    some_method(file_obj)

Это превращает функцию, которая должна выполнять ввод-вывод, в чистую (r) функцию, которая просто выполняет итерацию над файловоподобным объектом любой. Чтобы проверить это, вам не нужно имитировать open или каким-либо образом воздействовать на файловую систему; просто создайте объект StringIO для использования в качестве аргумента:

def test_it(self):
    f = StringIO.StringIO("line1\nline2\n")
    some_method(f)

(Если вы все еще чувствуете необходимость написать и протестировать оболочку вроде

def some_wrapper(file_name):
    with open(file_name, 'r') as file_obj:
        some_method(file_obj)

обратите внимание, что mocked open вам не нужен, чтобы делать что-то конкретное. Вы тестируете some_method отдельно, поэтому единственное, что вам нужно сделать для тестирования some_wrapper, - это убедиться, что возвращаемое значение open передается в some_method. open, в этом случае, может быть простой старой имитацией без особого поведения.)

Другие вопросы по теме