Python довольно буквально настроен.Выражение pg.connect()
переводится в:
- Поиск имени
pg
- Поиск атрибута
connect
для этого. - Вызовите его с пустымАргументы.
Таким образом, выражение pg.connect
получит метод, но не вызовет его.И, на самом деле, вы можете использовать этот метод в качестве обычного значения и передавать его, так что my_connect = pg.connect
полностью допустим, и вы можете позже вызвать my_connect()
.
Мы можем увидеть, что происходит сdis
module:
import dis
dis.dis(lambda: pg.connect())
1 0 LOAD_GLOBAL 0 (pg)
2 LOAD_ATTR 1 (connect)
4 CALL_FUNCTION 0
6 RETURN_VALUE
Поскольку Mock
хочет притвориться разными объектами, он переопределяет стандартные методы __getattribute__
и __call__
и __eq__
dunder.В то время как объекты имеют нормальный способ предоставления атрибутов, __getattribute__
позволяет классу делать все, что он хочет, если атрибут запрашивается.
Когда вы называете его как Mock(spec=psycopg2)
, он будет делать две основные вещи иначе, чемMock()
:
- Он корректирует свое поле
__class__
, чтобы заявить, что оно psycopg2
. - Проверьте psycopg2 на предмет допустимых свойств и увеличьте
AttributeError
, если вы пытаетесь получитьполе, которого нет.
Но, кроме того, оно все еще ведет себя как обычный макет.Если вы вызываете yourMock.foo
, он не знает, является ли это свойство или метод.Он просто возвращает новый экземпляр Mock
, и поскольку этот экземпляр может вызываться, вы можете вызывать его как функцию.
Обновление: если вы задаете спецификацию на Mock
, это ограничивает доступные атрибуты.Чтобы добавить атрибут, не предоставленный спецификацией, вам просто нужно установить его во время инициализации:
cursor_mock = Mock(side_effect=psycopg2.DatabaseError)
Mock(spec=psycopg2.connect, cursor=cursor_mock)
Вы также хотели это поведение: pg.connect().cursor().execute.side_effect = someexception()
.Как правило, вы можете изменить return_value
:
cursor_mock = Mock()
cursor_mock.return_value.execute.side_effect = SomeException
# Same as:
cursor_mock = Mock(return_value=Mock(execute=Mock(side_effect=SomeException)))
Вы также можете сделать cursor_mock().execute.side_effect = SomeException
, но это загрязняет cursor_mock.mock_calls
.
Небольшое замечание: я использую Mock
для краткости, и MagicMock
- то же самое, просто добавив несколько более сложных методов.