【问题标题】:How to mock nested functions?如何模拟嵌套函数?
【发布时间】:2012-09-13 10:37:52
【问题描述】:

我使用的模拟库是...mock

我在尝试为函数(遗留代码)编写测试用例时遇到了这个“模拟嵌套函数”问题。

这个函数使用了一个非常复杂的嵌套函数,对其他模块有很大的依赖。

我想知道是否可以使用 mock 模拟嵌套函数。

【问题讨论】:

    标签: python unit-testing mocking closures


    【解决方案1】:

    您是否尝试用模拟对象替换嵌套函数?如果是这样,无论函数多么复杂,这都相当简单。您可以使用 MagicMock 替换几乎任何 python 对象。

    如果你需要模拟一个有返回值的函数,你只需设置MagicMockreturn_value参数即可。它看起来像这样:

    >>> super_nested_mock = mock.MagicMock()
    >>> super_nested_mock.return_value = 42
    >>> super_nested_mock()
    42
    

    但是,如果您尝试测试另一段代码,该代码在内部某处调用您的 super_nested 函数,并且想要模拟它,则需要使用 patch。在模拟库中,它看起来像这样:

    with patch('super_nested') as super_nested_mock:
        super_nested_mock.return_value = "A good value to test with"
        assert my_function_that_calls_super_nested(5) == 20
    

    在这里,with 块中通常调用 super_nested 的任何内容都会调用 super_nested_mock 并返回您设置的值。

    您需要在补丁调用中添加的内容有些微妙。主要是,您希望修补对象,因为您正在测试的模块会看到它。更多说明请参见“where to patch”。

    【讨论】:

    • 这行不通,因为嵌套函数只存在于我要测试的函数中。所以patch无法直接定位替换。
    • 我明白了,我想我误解了你到底想测试什么。为了后代,我将把它留在这里。不过祝你好运。
    【解决方案2】:

    我看到这样做的唯一方法是动态创建外部函数的副本,使用模拟函数的代码修改函数的代码对象常量:

    Does an equivalent of override exist for nested functions?

    【讨论】:

      【解决方案3】:

      一种选择是更改您的函数,以便它可以选择接受要调用的函数,例如如果你有:

      def fn_to_test():
        def inner_fn():
          return 1
        return inner_fn() + 3
      

      改成:

      def fn_to_test( inner_fn = null )
        def inner_fn_orig():
          return 1
        if inner_fn==null:
          inner_fn = inner_fn_orig
        return fn() + 3
      

      然后“真正的”使用将获得正确的内部功能,并且在您的测试中您可以提供自己的。

      fn_to_test() # calls the real inner function
      def my_inner_fn():
        return 3
      fn_to_test( inner_fn=my_inner_fn ) # calls the new version
      

      你也可以这样做:

      def fn_to_test():
        def inner_fn_orign():
          return 1
        inner_fn = inner_fn_orig
        try:
          inner_fn = fn_to_test.inner_fn
        excecpt AttributeError:
          pass
        return inner_fn() + 3
      

      这样你只需定义覆盖:

      fn_to_test() # calls the real inner function
      def my_inner_fn():
        return 3
      fn_to_test.inner_fn = my_inner_fn
      fn_to_test() # calls the new version
      

      【讨论】:

      • 干得好,它确实有效,但这实际上是一种反模式。它会导致大量的臃肿和其他不良情况来创建额外的代码,使事情在测试中工作。相反,我们应该让测试按原样为代码工作。一种非常相似但仍遵守最佳实践的方法是在您的实际 func 中创建一个变量(不是参数),使用您的内部 func 来设置变量并在测试中,您可以模拟该内部函数,以便您的实际函数的参数完全正如你所料。这还可以让您对需要模拟的客户端等事物进行细粒度测试。
      【解决方案4】:

      例如,您需要模拟来自 Google DRIVE API 的嵌套函数调用(链函数)

      result = get_drive_service().files().insert(body='body', convert=True).execute()   
      

      所以你需要通过函数来​​打补丁:service_mock()、files()、insert(),直到最后一个execute()响应:

      from mock import patch
      with patch('path.to.import.get_drive_service') as service_mock:
         service_mock.return_value.files.return_value.insert.\
         return_value.execute.return_value = {'key': 'value', 'status': 200}
      

      主要方案: first.return_value.second.return_value.third.return_value.last.return_value = rsp

      【讨论】:

      • 这节省了我的周末。请注意,嵌套属性不需要与 return_value 链接。
      • 我对这种方法的问题是它必须先模拟外部服务函数,然后才能模拟内部嵌套函数。有时,我们只想模拟内部函数本身。
      • 除了最后一个元素外,不需要使用return_value,即以下也可以:first().second().third().last.return_value = rsp
      • @AlexK,这对于特定条件可能是正确的。我面前有一些东西说不通。在我们之间,您至少可以说“如有疑问,请使用return_value 处理每个嵌套方法”。
      • @Cody,有什么例子说明什么时候不起作用?
      猜你喜欢
      • 2021-02-27
      • 2011-07-18
      • 2021-10-18
      • 1970-01-01
      • 2021-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多