我正在处理一系列用户定义的对象。它看起来类似于以下内容:
class Thing(object):
def __init__(self, x, y):
self.x = x
self.y = y
我当前正在测试的方法具有类似于以下内容的功能:
def my_function(things):
x_calc = calculate_something(t.x for t in things)
y_calc = calculate_something(t.y for t in things)
return x_calc / y_calc
我面临的问题是测试对calculate_something
的调用。我想断言这些电话发生了,如下所示:
calculateSomethingMock.assert_any_call(the_sequence)
我不在乎传递到calculate_something
的序列的顺序,但我确实关心元素都存在。我可以将生成器函数包装在对set
的调用中,但我觉得我的测试不应该决定将哪种类型的序列传递到calculate_something
中。我应该能够传递任何类型的序列。我也可以创建一个生成序列的方法,而不是使用生成器语法并模拟该方法,但这似乎有点矫枉过正。
怎样才能最好地构建这个断言,或者我在这里测试的麻烦是否表明代码结构不佳?
我正在使用带有模拟 1.0.1 的 Python 2.7.3。
(对于任何觉得有必要对此发表评论的人,我知道我是在最后进行测试,这并不被认为是最好的做法。
编辑:
在看了这个题为"为什么你没有得到格雷戈里·莫克的模拟对象"的精彩演讲后,我重新考虑了我是否应该嘲笑calculate_something
方法。
查看模拟文档,有一个call_args_list可以做你想做的事。
因此,您将在测试中模拟calculate_something
。
calculate_something = Mock(return_value=None)
my_function
完成后,您可以通过执行以下操作检查传递的参数:
calculate_something.call_args_list
这将返回对它进行的所有调用的列表(传递了相应的元素)。
编辑:
(对不起,我花了这么长时间,我不得不在我的机器上安装 Python3.3)
mymodule.py
class Thing:
...
def calculate_something:
...
def my_function(things):
# Create the list outside in order to avoid a generator object
# from being passed to the Mock object.
xs = [t.x for t in things]
x_calc = calculate_something(xs)
ys = [t.y for t in things]
y_calc = calculate_something(ys)
return True
test_file.py
import unittest
from unittest.mock import patch, call
import mymodule
class TestFoo(unittest.TestCase):
# You can patch calculate_something here or
# do so inside the test body with
# mymodule.calcualte_something = Mock()
@patch('mymodule.calculate_something')
def test_mock(self, mock_calculate):
things = [mymodule.Thing(3, 4), mymodule.Thing(7, 8)]
mymodule.my_function(things)
# call_args_list returns [call([3, 7]), call([4, 8])]
callresult = mock_calculate.call_args_list
# Create our own call() objects to compare against
xargs = call([3, 7])
yargs = call([4, 8])
self.assertEqual(callresult, [xargs, yargs])
# or
# we can extract the call() info
# http://www.voidspace.org.uk/python/mock/helpers.html#mock.call.call_list
xargs, _ = callresult[0]
yargs, _ = callresult[1]
xexpected = [3, 7]
yexpected = [4, 8]
self.assertEqual(xargs[0], xexpected)
self.assertEqual(yargs[0], yexpected)
if __name__ == '__main__':
unittest.main()
一段时间没有接触我最初使用的代码了,但我一直在重新考虑我的测试方法。我一直在努力更加小心我所做的事情,不要嘲笑。我最近意识到,我无意识地开始遵循这个经验法则:如果它使我的测试更短更简单,就嘲笑它,如果它使测试更复杂,就不要管它。在这种方法的情况下,简单的输入/输出测试就足够了。没有数据库或文件等外部依赖项。所以简而言之,我认为我的问题的答案是,"我不应该嘲笑calculate_something
。这样做会使我的测试更难阅读和维护。