#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()
затем assert2. Это то, что я делаю внутри самого теста, но когда я пытаюсь
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()].