Rubyist在这里编写Python。我有一些看起来像这样的代码:
result = database.Query('complicated sql with an id: %s' % id)
database.Query
被模拟了,我想测试ID是否正确注入而不将整个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])
更好的想法?
答案 0 :(得分:51)
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)
答案 1 :(得分:11)
您可以使用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
答案 2 :(得分:2)
您可以使用match_equality
中的PyHamcrest library来包装来自同一库的matches_regexp
匹配器:
from hamcrest.library.integration import match_equality
with patch(database) as MockDatabase:
instance = MockDatabase.return_value
...
expected_arg = matches_regexp(id)
instance.Query.assert_called_once_with(match_equality(expected_arg))
Python的unittest.mock
文档中也提到了此方法:
从1.5版开始,Python测试库PyHamcrest以其相等匹配器(hamcrest.library.integration.match_equality)的形式提供了类似的功能,在这里可能有用。
如果您不想使用PyHamcrest,则上面链接的文档还显示了如何通过使用__eq__
方法定义一个类来编写自定义匹配器(如falsetru
答案中所建议):
class Matcher:
def __init__(self, compare, expected):
self.compare = compare
self.expected = expected
def __eq__(self, actual):
return self.compare(self.expected, actual)
match_foo = Matcher(compare, Foo(1, 2))
mock.assert_called_with(match_foo)
您可以用自己的正则表达式匹配替换对self.compare
的调用,如果找不到则返回False
或使用您选择的描述性错误消息引发AssertionError
。
答案 3 :(得分:-1)
我总是写单元测试,以便反映“现实世界”。除了the ID gets injected in correctly
之外,我真的不知道你要测试的 。
我不知道database.Query
应该做什么,但我想它应该创建一个你可以调用或稍后传递给连接的查询对象?
您可以通过测试此方法来获取真实世界示例的最佳方式。做一些简单的事情,比如检查查询中是否出现id太容易出错。我经常看到人们想要在他们的单元测试中做出神奇的东西,这总是会导致问题。保持您的单元测试简单和静态。在你的情况下,你可以这样做:
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
来阻止单元测试进入数据库:
@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
方法。 %
格式可能会在将来消失。有关详细信息,请参阅this question。
我也不禁觉得测试字符串格式是多余的。如果'test %s' % 'test'
不起作用,那就意味着Python出了问题。只有你想测试自定义查询构建才有意义。例如插入字符串应该引用,数字不应该引用,转义特殊字符等。