如何用mock修补模块内部函数?
这里提到的“内部函数”,指的是在定义它的同一个模块里被调用的函数。
我在单元测试中使用了mock库,特别是patch装饰器。这些测试是Django的单元测试,但这对任何Python测试都适用。
我有一个模块,里面有好几个函数,它们之间会相互调用。例如(这段代码是虚构的,忽略小数点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, price)。
但是,当我尝试替换这两个函数时,替换在外部似乎有效(我可以在测试中导入被替换的部分并打印出来,得到预期的值),但在内部似乎没有效果(代码的结果表现得好像没有进行替换一样)。
我的测试代码是这样的(同样是虚构的):
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是否可以替换像这样在内部使用的函数,还是说我没戏了?
5 个回答
我想提供一个除了被接受的答案之外的解决方案。你还可以在其他模块导入之前,先对这个模块进行修补,然后在测试结束时再把修补去掉。
#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()
另一个选择是明确地对这个函数调用补丁(patch):
mock.patch('function_name')
并且要支持直接运行或者通过 py.test 等工具运行:
mock.patch(__name__ + '.' + 'function_name')
答案是:整理你的导入代码
@patch('mymodule.TAX_LOCATION', '')
确实正确地进行了修改,但当时我们的导入方式非常混乱——有时候我们导入的是 mymodule.build_cart
,有时候导入的是 project.mymodule.build_cart
——所以“完整”的导入根本没有被修改。Mock 不能自动知道这两种不同的导入方式……除非我们明确告诉它。
现在我们已经统一了所有的导入方式,使用了更长的路径,这样一来,程序的表现就好多了。