如何使用mock修补模块的内部函数?

时间:2011-03-17 15:21:48

标签: python mocking patch

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

我在单元测试中使用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

有谁知道模拟是否可以修补这样内部使用的功能,或者我运气不好?

4 个答案:

答案 0 :(得分:14)

答案:清理您的darned进口

@patch('mymodule.TAX_LOCATION', '')确实对事情进行了适当补丁,但由于当时我们的导入非常随意 - 有时我们导入mymodule.build_cart,有时我们导入project.mymodule.build_cart - “满”的实例“进口根本没有打补丁。无论如何,Mock不会被要求知道两条独立的导入路径......无论如何都没有明确说明。

我们已经在更长的路径上标准化了所有进口产品,现在情况表现得更好。

答案 1 :(得分:7)

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

mock.patch('function_name')

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

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

答案 2 :(得分: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()

答案 3 :(得分:1)

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