【问题标题】:How to patch a module's internal functions with mock?如何用 mock 修补模块的内部功能?
【发布时间】:2011-07-17 12:04:18
【问题描述】:

“内部函数”是指从定义它的同一模块中调用的函数。

我在单元测试中使用mock 库,特别是patch 装饰器。它们是 Django 单元测试,但这应该适用于任何 python 测试。

我有一个具有多个功能的模块,其中许多功能相互调用。例如(虚构代码,忽略缺少的decimal.Decimal):

TAX_LOCATION = 'StateName, United States'

def add_tax(price, user):
    tax = 0
    if TAX_LOCATION == 'StateName, UnitedStates':
        tax = price * .75
    return (tax, price+tax)

def build_cart(...):
    # build a cart object for `user`
    tax, price = add_tax(cart.total, cart.user)
    return cart

这些是更深层调用链的一部分(func1 -> func2 -> build_cart -> add_tax),所有这些都在同一个模块中。

在我的单元测试中,我想禁用税收以获得一致的结果。正如我所看到的,我的两个选项是 1)修补 TAX_LOCATION(例如,使用空字符串)以便 add_tax 实际上不做任何事情或 2)修补 add_tax 以简单地返回(0,价格)。

但是,当我尝试修补其中任何一个时,补丁似乎在外部工作(我可以在测试中导入修补的部分并将其打印出来,得到预期值),但在内部似乎没有效果(结果我从代码中获取的行为就像未应用补丁一样)。

我的测试是这样的(又是虚构的代码):

from mock import patch
from django.test import TestCase

class MyTests(TestCase):

    @patch('mymodule.TAX_LOCATION', '')
    def test_tax_location(self):
        import mymodule
        print mymodule.TAX_LOCATION # ''
        mymodule.func1()
        self.assertEqual(cart.total, original_price) # fails, tax applied

    @patch('mymodule.add_tax', lambda p, u: (0, p))
    def test_tax_location(self):
        import mymodule
        print mymodule.add_tax(50, None) # (0, 50)
        mymodule.func1()
        self.assertEqual(cart.total, original_price) # fails, tax applied

有谁知道 mock 是否可以像这样修补内部使用的函数,还是我不走运?

【问题讨论】:

  • 测试函数中的任何导入都会看到修补后的值,但我怀疑问题仍然是“命名空间”问题。 (即修补的位置和它们的使用位置不匹配)。你没有显示真正的代码 - mymodule.func1() 没有显示,也没有显示“cart”来自哪里 - 所以我无法诊断问题(我是 mock 的作者)。跨度>
  • @fuzzyman,具有讽刺意味的是,到目前为止,我在尝试生成一个重现问题的简短示例时完全没有成功(我在简单的 python 模块和简单的 django 项目中都尝试了几件事,一切都按预期运行)。不过,我在其中使用的项目是一个非常庞大的 django 项目,所以在这一点上,我相信导入时会发生一些受污染的事情(从这里的示例,从 build_cart 中,print sys.modules['app.views'].add_tax 打印一个 Mock 对象而print add_tax 打印原始函数)。

标签: python mocking patch


【解决方案1】:

答案:清理你该死的导入

@patch('mymodule.TAX_LOCATION', '') 确实做了适当的修补,但是由于我们当时的导入非常随意——有时我们导入了mymodule.build_cart,有时我们导入了project.mymodule.build_cart——“完整”导入的实例没有在全部。无论如何,不​​能指望 Mock 知道这两个单独的导入路径......没有被明确告知。

从那以后,我们在更长的路径上对所有导入进行了标准化,现在事情的表现要好得多。

【讨论】:

    【解决方案2】:

    另一种选择是在函数上显式调用补丁:

    mock.patch('function_name')
    

    并支持直接运行或从 py.test 等运行:

    mock.patch(__name__ + '.' + 'function_name')
    

    【讨论】:

    • 这是我模拟在同一模块中定义的函数所需要的
    【解决方案3】:

    我想添加除已接受的解决方案之外的解决方案。您还可以在将模块导入任何其他模块之前对其进行修补,并在测试用例结束时删除补丁。

    #import some modules that don't use module you are going to patch
    import unittest
    from mock import patch
    import json
    import logging
    ...
    
    
    patcher = patch('some.module.path.function', lambda x: x)
    patcher.start()
    
    import some.module.path
    
    class ViewGetTests(unittest.TestCase):
    
      @classmethod
      def tearDownClass(cls):
          patcher.stop()
    

    【讨论】:

      【解决方案4】:

      我很确定您的问题是您在测试函数中导入了“mymodule”,因此补丁装饰器没有机会实际打补丁。在模块顶部进行导入,就像任何其他导入一样。

      【讨论】:

      • 如果是这种情况,为什么从测试函数中调用修补函数时会按预期运行?除此之外,当我完全忽略导入时,我仍然会出现不良行为。不过,感谢您的尝试。
      【解决方案5】:

      如果您的模块位于一个包含 from [module_file] import * 的 __init__.py 文件的文件夹中,请确保您的补丁参数具有文件夹和文件名 (module_folder.module_file),否则补丁将成功(没有 'module 没有这个属性的错误)而不是函数(调用将转到实际函数而不是模拟函数),无论被测函数如何导入。

      【讨论】:

        猜你喜欢
        • 2012-09-19
        • 2014-08-29
        • 1970-01-01
        • 2014-09-02
        • 1970-01-01
        • 1970-01-01
        • 2012-06-24
        • 1970-01-01
        • 2020-03-16
        相关资源
        最近更新 更多