Строгий макет в питоне

Есть ли какой-нибудь эквивалент строгих издевательств в питоне? Некоторый механизм для сообщения о непреднамеренном вызове фиктивных методов (action.step2() в этом примере), как в GoogleMock Framework.

class Action:
    def step1(self, arg):
        return False

    def step2(self, arg):
        return False

def algorithm(action):
    action.step1('111')
    action.step2('222')
    return True

class TestAlgorithm(unittest.TestCase):
    def test_algorithm(self):
        actionMock = mock.create_autospec(Action)
        self.assertTrue(algorithm(actionMock))
        actionMock.step1.assert_called_once_with('111')

3 ответа

Решение

Похоже, это не поддерживается из коробки. Однако есть как минимум два подхода к достижению того же результата.

Проходной список разрешенных участников

Согласно макету документации

spec: это может быть либо список строк, либо существующий объект (класс или экземпляр), который действует как спецификация для фиктивного объекта. Если вы передаете объект, то список строк формируется путем вызова dir для объекта (исключая неподдерживаемые магические атрибуты и методы). Доступ к любому атрибуту, отсутствующему в этом списке, вызовет AttributeError.

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

actionMock = mock.create_autospec(Action)

в

actionMock = mock.Mock(spec=['step1'])

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

all_members = dir(Action)  # according to docs this is what's happening behind the scenes
all_members.remove('step2')  # remove all unwanted methods 
actionMock = mock.Mock(spec=all_members)

Установка исключений для ограниченных методов

Альтернативный подход состоит в том, чтобы явно устанавливать ошибки для методов, которые вы не хотите вызывать:

def test_algorithm(self):
    actionMock = mock.create_autospec(Action)
    actionMock.step2.side_effect = AttributeError("Called step2") # <<< like this
    self.assertTrue(algorithm(actionMock))
    actionMock.step1.assert_called_once_with('111')

Это также имеет некоторые ограничения: вы должны установить ошибки, а также ожидания.

В качестве последнего замечания, одно радикальное решение проблемы будет патч mock добавить strict параметр для Mock конструктор и отправить запрос на извлечение. Чем либо это будет принято, либо mock сопровождающие укажут, как этого добиться.:)

Да, это возможно с помощью spec= а также autospec= аргументы. См. Ложную документацию по Autospeccing для получения дополнительной информации. В вашем примере это станет:

action_mock = mock.Mock(spec=Action)

или же:

action_mock = mock.Mock('Action', autospec=True)

Другая возможность:

Проверка call_count индивидуально на ограниченных методах

Убедиться, что call_count ноль для методов, которые не должны вызываться.

class TestAlgorithm(unittest.TestCase):
    def test_algorithm(self):
        actionMock = mock.create_autospec(Action)
        self.assertTrue(algorithm(actionMock))
        actionMock.step1.assert_called_once_with('111')
        self.assertEqual(actionMock.step2.call_count, 0) # <<< like this

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

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