关于python:unittest.mock:声明方法参数的部分匹配

unittest.mock: asserting partial match for method argument

鲁比派在这里写Python。我有一些代码看起来有点像这样:

1
result = database.Query('complicated sql with an id: %s' % id)

database.Query是模拟出来的,我想测试ID是否正确注入,而不需要将整个SQL语句硬编码到我的测试中。在Ruby/RR中,我可以这样做:

1
mock(database).query(/#{id}/)

但我看不出在unittest.mock中设置"选择性模拟"的方法,至少没有一些复杂的side_effect逻辑。所以我尝试在断言中使用regexp:

1
2
3
4
with patch(database) as MockDatabase:
  instance = MockDatabase.return_value
  ...
  instance.Query.assert_called_once_with(re.compile("%s" % id))

但这也不管用。这种方法确实有效,但很难看:

1
2
3
4
with patch(database) as MockDatabase:
  instance = MockDatabase.return_value
  ...
  self.assertIn(id, instance.Query.call_args[0][0])

更好的主意?


1
2
3
4
5
6
7
8
9
10
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))
...

编辑:抢先要求匹配字符串

1
2
3
4
5
6
7
8
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)


您只需使用unittest.mock.ANY

1
2
3
4
5
6
7
8
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任何


我总是编写单元测试,以便它们反映"真实世界"。我真的不知道你想测试什么,除了the ID gets injected in correctly

我不知道EDOCX1[1]应该做什么,但我想它应该创建一个查询对象,您可以稍后调用或传递到连接?

最好的测试方法是以现实世界为例。做一些简单的事情,比如检查ID是否出现在查询中,这太容易出错了。我经常看到人们想在单元测试中做神奇的事情,这总是导致问题。保持单元测试的简单和静态。在您的情况下,您可以:

1
2
3
4
5
6
7
8
9
10
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.Querydatabase.execute之类的东西。Query中的大写表示您创建一个对象,如果对象都是小写的,则表示您调用了一个函数。这更像是一个命名约定和我的意见,但我只是把它扔到了外面。-)

如果database.Query直接查询,则最好修补它调用的方法。例如,如果它看起来像这样:

1
2
3
def Query(self, query):
    self.executeSQL(query)
    return query

您可以使用mock.patch来防止单元测试进入数据库:

1
2
3
4
5
@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出了问题。只有当您想要测试自定义查询构建时,它才有意义。例如,插入字符串应加引号,数字不应加引号,转义特殊字符等。