5

我正在处理一系列用户定义的对象。它看起来类似于以下内容:

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. 我应该能够以任何顺序传递它。我也可以创建一个生成序列的方法,而不是使用生成器语法并模拟该方法,但这似乎有点矫枉过正。

我怎样才能最好地构建这个断言,或者我在这里测试的问题是否表明代码结构不佳?

我正在使用 Python 2.7.3 和 Mock 1.0.1。

(对于任何觉得有必要对此发表评论的人,我知道我在最后做测试,这不是最好的做法。)

编辑:

在观看了 Gregory Moeck 的题为“Why You Don't Get Mock Objects”的精彩演讲后,我重新考虑是否应该嘲笑该calculate_something方法。

4

2 回答 2

7

查看 Mock 文档,有一个call_args_list 可以满足您的需求。

所以你会模拟calculate_something你的测试。

calculate_something = Mock(return_value=None)

完成后,my_function您可以通过以下方式检查传递的参数:

calculate_something.call_args_list

这将返回对其进行的所有调用的列表(传递了相应的元素)。

编辑

(抱歉拖了我这么久,我不得不在我的机器上安装 Python3.3)

我的模块.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

测试文件.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()
于 2013-01-22T19:40:02.147 回答
2

我已经有一段时间没有接触过我最初使用的代码了,但是我一直在重新考虑我的测试方法。我一直在努力对自己所做的事情更加小心,不要嘲笑。我最近意识到我开始不知不觉地开始遵循这条经验法则:如果它使我的测试更短更简单,就模拟一些东西,如果它让测试变得更复杂,就别管它。在这种方法的情况下,简单的输入/输出测试就足够了。没有像数据库或文件这样的外部依赖项。所以简而言之,我认为我的问题的答案是,“我不应该嘲笑calculate_something。” 这样做会使我的测试更难阅读和维护。

于 2013-05-09T06:10:31.897 回答