unittest.mock: утверждение частичного совпадения для аргумента метода
Rubyist пишет Python здесь. У меня есть код, который выглядит примерно так:
result = database.Query('complicated sql with an id: %s' % id)
Сценарий database.Query
смоделирован, и я хочу проверить, правильно ли вводится идентификатор без жесткого кодирования всего оператора SQL в моем тесте. В Ruby/RR я бы сделал это:
mock(database).query(/#{id}/)
Но я не вижу способа настроить "селективный макет", подобный этому, в unittest.mock, по крайней мере, без какой-либо волосатой логики side_effect
. Поэтому я попытался использовать регулярное выражение в утверждении:
with patch(database) as MockDatabase:
instance = MockDatabase.return_value
...
instance.Query.assert_called_once_with(re.compile("%s" % id))
Но это тоже не работает. Этот подход работает, но он уродлив:
with patch(database) as MockDatabase:
instance = MockDatabase.return_value
...
self.assertIn(id, instance.Query.call_args[0][0])
Лучшие идеи?
Ответы
Ответ 1
import mock
class AnyStringWith(str):
def __eq__(self, other):
return self in other
...
result = database.Query('complicated sql with an id: %s' % id)
database.Query.assert_called_once_with(AnyStringWith(id))
...
Преимущественно требуется соответствующая строка
def arg_should_contain(x):
def wrapper(arg):
assert str(x) in arg, "'%s' does not contain '%s'" % (arg, x)
return wrapper
...
database.Query = arg_should_contain(id)
result = database.Query('complicated sql with an id: %s' % id)
UPDATE
Используя такие библиотеки, как callee
, вам не нужно реализовывать AnyStringWith
.
from callee import Contains
database.Query.assert_called_once_with(Contains(id))
https://callee.readthedocs.io/en/latest/reference/operators.html#callee.operators.Contains
Ответ 2
Вы можете просто использовать unittest.mock.ANY
:)
from unittest.mock import Mock, ANY
def foo(some_string):
print(some_string)
foo = Mock()
foo("bla")
foo.assert_called_with(ANY)
Как описано здесь - https://docs.python.org/3/library/unittest.mock.html#any
Ответ 3
Я всегда пишу свои модульные тесты, чтобы они отражали "реальный мир". Я не знаю, что вы хотите проверить, кроме the ID gets injected in correctly
.
Я не знаю, что должен делать database.Query
, но я предполагаю, что он должен создать объект запроса, который вы можете вызвать или передать потом позже?
Лучший способ проверить это на примере реального мира. Выполнение чего-то простого, например, проверка наличия идентификатора в запросе слишком подвержена ошибкам. Я часто вижу людей, желающих делать магические вещи в своих модульных тестах, этот всегда приводит к проблемам. Храните ваши модульные тесты простыми и статичными. В вашем случае вы можете сделать:
class QueryTest(unittest.TestCase):
def test_insert_id_simple(self):
expected = 'a simple query with an id: 2'
query = database.Query('a simple query with an id: %s' % 2)
self.assertEqual(query, expected)
def test_insert_id_complex(self):
expected = 'some complex query with an id: 6'
query = database.Query('some complex query with an id: %s' 6)
self.assertEqual(query, expected)
Если database.Query
выполняет непосредственно запрос в базе данных, вам может потребоваться вместо этого использовать что-то вроде database.Query
или database.execute
. Столица в Query
подразумевает, что вы создаете объект, если все в нижнем регистре означает, что вы вызываете функцию. Это скорее соглашение об именах и мое мнение, но я просто бросаю его туда.; -)
Если непосредственно запросы database.Query
вы можете исправить метод, который он вызывает. Например, если он выглядит так:
def Query(self, query):
self.executeSQL(query)
return query
Вы можете использовать mock.patch
для предотвращения перехода unit test в базу данных:
@mock.patch('database.executeSQL')
def test_insert_id_simple(self, mck):
expected = 'a simple query with an id: 2'
query = database.Query('a simple query with an id: %s' % 2)
self.assertEqual(query, expected)
В качестве дополнительного совета попробуйте использовать метод str.format
. Форматирование %
может исчезнуть в будущем. См. этот вопрос для получения дополнительной информации.
Я также не могу не чувствовать, что тестирование форматирования строк является избыточным. Если 'test %s' % 'test'
не работает, это означает, что с Python что-то не так. Это имело бы смысл, если бы вы захотели протестировать построение пользовательских запросов. например вставлять строки должны быть указаны, цифры не должны, избегать специальных символов и т.д.