Пытаюсь издевательски исправить функцию, но получаю PIL\Image.py’> не имеет атрибута «сохранить»

#python #mocking #pytest

Вопрос:

Я использую pytest-mock, но исключение создается из кода mock.patch, я проверил, что та же ошибка возникает, если я использую синтаксис @mock.patch декоратора.

MRE (убедитесь, что у вас установлены pytest и pytest-макет, не нужно ничего импортировать):

 def test_image(mocker):
    mocker.patch("PIL.Image.save")
 

Теперь запустите pytest в этом модуле.

Ошибка:

 E           AttributeError: <module 'PIL.Image' from 'c:\users\...\site-packages\PIL\Image.py'> does not have the attribute 'save'
 

Я ясно вижу, что Image.py содержит функцию с именем save, но функции не считаются атрибутами? Я никогда не слышал, чтобы это слово использовалось для обозначения содержимого модуля.

Ответ №1:

save является методом экземпляра PIL.Image.Image класса, а не PIL.Image модуля.

Вы должны реализовать патч следующим образом:

 def test_image(mocker):
    mocker.patch("PIL.Image.Image.save")
 

Если вам нужно сделать утверждения о том, что save метод вызывается в Image экземпляре, вам нужно имя, привязанное к макету.
Вы можете реализовать это, издеваясь над Image классом и привязывая Mock экземпляр к его save методу . Например,

 def test_image(mocker):
    # prepare
    klass = mocker.patch("PIL.Image.Image")
    instance = klass.return_value
    instance.save = mocker.Mock() 
    
    # act
    # Do operation that invokes save method on `Image` instance

    # test
    instance.save.assert_called()
 

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

1. Я совершенно упустил из виду, что это был метод, а не простая функция, спасибо. Одна вещь, в которой я все еще немного смущен, — это то, как я теперь ссылаюсь на исправленный метод в утверждениях. ПИЛ. Изображение. Image.save.assert_not_called (), кажется, работает, но это означает, что мне нужно импортировать PIL в мой тестовый модуль, что кажется немного странным, так как мне не нужен был этот импорт, чтобы исправить его.

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

3. Еще раз спасибо, я действительно смог упростить ваш пример и просто назначить имя, чтобы mocker.patch("PIL.Image.Image.save") затем утверждать это. Это помогло мне, так как оно одновременно короче и позволяет избежать исправления всего класса, которое нарушило бы другую часть теста.