Подтвердить что ты не робот

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])

Лучшие идеи?

4b9b3361

Ответ 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 что-то не так. Это имело бы смысл, если бы вы захотели протестировать построение пользовательских запросов. например вставлять строки должны быть указаны, цифры не должны, избегать специальных символов и т.д.