【问题标题】:How can I assert calls that accept sequence arguments with Python Mock?如何使用 Python Mock 断言接受序列参数的调用?
【发布时间】:2013-01-22 17:50:02
【问题描述】:

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

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。

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

编辑:

看了this marvelous talk entitled "Why You Don't Get Mock Objects by Gregory Moeck"之后,我重新考虑是否应该嘲笑calculate_something方法。

【问题讨论】:

    标签: python unit-testing mocking


    【解决方案1】:

    查看 Mock 文档,有一个 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 的参数更改为其他数据结构,我是否希望测试失败?或者相反,如果我更改测试以检查不同数据类型的调用,我是否需要修改方法以使其再次通过?
    • 是的,但请记住这是一个示例。您可以通过calculate_something 任何其他数据结构,只需更新测试以反映这一点(通过更改预期值)。毕竟,您希望测试通过。您已经了解了如何使用call_args_list 将参数传递给calculate_something。之后,只需将传递的结构化数据与您期望的数据进行比较即可。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-08
      • 1970-01-01
      • 1970-01-01
      • 2012-08-24
      • 2017-09-08
      • 1970-01-01
      • 1970-01-01
      • 2019-07-17
      相关资源
      最近更新 更多