Python unittest patch макет всего класса

#python #unit-testing #mocking #pytest #patch

#python #модульное тестирование #издевательство #pytest #исправление

Вопрос:

У меня есть класс, который я хочу исправить в своих unittests.

 class OriginalClass():
   def method_a():
     # do something
   
   def method_b():
     # do another thing
  

Теперь я создал другой класс для его исправления, поэтому код для его исправления выглядит так

 class MockClass(OriginalClass):
    def method_a():
        # This will override the original method and return custom response for testing.

patcher = patch('OriginalClass', new=MockClass)
mock_instance = patcher.start()    
  

Это работает именно так, как я хочу, и я могу возвращать любые ответы, необходимые для моих unittests.

Теперь эта проблема возникает, когда я хочу убедиться, что метод вызывается с правильными параметрами в unittests. Я пытался

 mock_instance.method_a.assert_called_once()
  

Но он завершается ошибкой AttributeError: 'function' object has no attribute 'assert_called_once' .

Как я могу протестировать вызовы методов здесь?

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

1. после patcher.start() этого сначала выполните вызов, oc = OriginalClass() а oc.method() затем assert

2. Это то, что я делаю внутри самого теста, но когда я пытаюсь assert_called_once , я получаю ошибку AttributeError: 'function' object has no attribute 'assert_called_once'

Ответ №1:

Ошибка атрибута: объект ‘function’ не имеет атрибута ‘assert_called_once’.

Как только макет объекта создан, method_a он не существует, вам нужно вызвать один раз m.method_a() перед assert .

     m = mock.create_autospec(OriginalClass)
    m.method_a()
    m.method_a.assert_called_once()
  

патч макет всего класса

Я воспринял это как макет всего класса и всех его методов, я бы взял пример отсюда https://docs.python.org/3.3/library/unittest.mock-examples.html

Применяя один и тот же патч к каждому методу тестирования, вот мой пример, исправьте весь основной класс как MockPrimay для каждого метода и каждого теста, setup or SetupClass для необходимых методов можно добавить даже весь класс, но не все методы, которые будут использоваться в тестах.

 from tests.lib.primary_secondary import Secondary


@mock.patch('tests.lib.primary_secondary.Primary')
class TestSecondaryMockPrimary(unittest.TestCase):

    def test_method_d(self, MockPrimary):
        
        MockPrimary().process()
        MockPrimary().process.return_value = 1
        oc = Secondary()
        self.assertEqual(oc.method_d(), 1)
        import tests
        self.assertIs(tests.lib.primary_secondary.Primary, MockPrimary)
  

Первичный необходим для вторичного для этого теста

 class Primary(object):

    def __init__(self, param):
        self._param = param

    def process(self):
        if self._param == 1:
            self._do_intermediate_process()
        self._do_process()


class Secondary(object):

    def __init__(self):
        self.scl = Primary(1)

    def method_d(self):
        return self.scl.process
  

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

1. Это устранит проблему assert_called_with , но мне все равно нужно возвращать значения mutliple на method_a основе переданных параметров.

2. @MahmoudAhmed, если вы хотите протестировать method_a, не издевайтесь над ним. Макет для тестирования других методов или классов

3. Позвольте мне объяснить. У меня есть класс, который вызывает внешнюю службу, я не хочу вызывать эту службу в своих тестах. Поэтому я издевался над этим классом с помощью другого класса. Каждая операция в моей системе включает вызов нескольких методов внутри этого класса несколько раз, поэтому мне нужно возвращать разные ответы с разными параметрами. Мне удалось добиться этого с помощью приведенной выше реализации. проблема в том, что я хочу написать тест, чтобы проверить, что система вызывает метод с правильными параметрами. Я не могу вызвать assert_called_with эти методы, поскольку это реальные функции, а не макет.

Ответ №2:

Я думаю wraps , что это может быть полезно здесь:

 from unittest.mock import patch

class Person:
    name = "Bob"
    def age(self):
        return 35

class Double(Person):
    def age(self):
        return 5


with patch('__main__.Person', wraps=Double()) as mock:
    print(mock.name)  # mocks data
    print(mock.age()) # runs real methods, but still spies their calls
    mock.age.assert_not_called()
  

Вывод:

 <MagicMock name='Person.name' id='139815250247536'>
5
...
raise AssertionError(msg)
AssertionError: Expected 'age' to not have been called. Called 1 times.
Calls: [call()].