如何使用模拟修补模块的内部函数?
我所说的“内部函数”是指从定义它的同一模块内调用的函数。
我正在使用 在我的单元测试中,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
有谁知道模拟是否可以修补像这样内部使用的函数,还是我运气不好?
By "internal function", I mean a function that is called from within the same module it is defined in.
I am using the mock library, specifically the patch decorators, in my unit tests. They're Django unit tests, but this should apply to any python tests.
I have one module with several functions, many of which call each other. For example (fictitious code, ignore the lack of 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
These are part of a deeper calling chain (func1 -> func2 -> build_cart -> add_tax), all of which are in the same module.
In my unit tests, I'd like to disable taxes to get consistent results. As I see it, my two options are 1) patch out TAX_LOCATION (with an empty string, say) so that add_tax doesn't actually do anything or 2) patch out add_tax to simply return (0, price).
However, when I try to patch either of these the patch seems to work externally (I can import the patched part inside the test and print it out, getting expected values), but seems to have no effect internally (the results I get from the code behave as if the patch were not applied).
My tests are like this (again, fictitious code):
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
Does anyone know if it's possible for mock to patch out functions used internally like this, or am I out of luck?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
答案:清理你该死的导入
@patch('mymodule.TAX_LOCATION', '')
确实适当地修补了东西,但因为我们当时的导入非常随意-- 有时我们导入mymodule.build_cart
,有时我们导入project.mymodule.build_cart
-- “完整”导入的实例根本没有修补。无论如何,在没有明确告知的情况下,不能期望 Mock 了解两个单独的导入路径。从那时起,我们已经在更长的路径上标准化了所有导入,现在事情表现得好多了。
The answer: Clean up your darned imports
@patch('mymodule.TAX_LOCATION', '')
did indeed patch things appropriately, but since our imports at the time were very haphazard -- sometimes we importedmymodule.build_cart
, sometimes we importedproject.mymodule.build_cart
-- instances of the "full" import were not patched at all. Mock couldn't be expected to know about the two separate import paths... without being told explicitly, anyway.We've since standardized all our imports on the longer path, and things behave much more nicely now.
另一种选择是在函数上显式调用 patch:
并支持直接运行或从 py.test 等运行:
another option is to explicitly call patch on the function:
and to support both running directly or from py.test etc:
我想添加除已接受的解决方案之外的解决方案。您还可以在将模块导入任何其他模块之前对其进行修补,并在测试用例末尾删除修补程序。
I'd like to add solution other than accepted one. You can also patch the module before it's been imported in any other modules and remove patch at the end of your test case.
我很确定你的问题是你在测试函数中导入“mymodule”,因此补丁装饰器没有机会实际修补。像任何其他导入一样,在模块顶部进行导入。
I'm pretty sure your problem is that you are importing 'mymodule' inside your test functions, and therefore the patch decorator has no chance of actually patching. Do the import at the top of the module, like any other import.
如果您的模块位于包含 __init__.py 文件的文件夹中,并且该文件具有
from [module_file] import *
,请确保您的补丁参数具有文件夹和文件名 (module_folder.module_file
),或者补丁会成功(没有“模块没有此属性”错误),但函数不会成功(调用将转到实际函数而不是模拟),无论测试中的函数如何导入。If your module is in a folder with an __init__.py file that has
from [module_file] import *
make sure your patch argument has the folder and file name (module_folder.module_file
), or the patch will succeed (no 'module does not have this attribute' error) but not function (calls will go to the actual function not the mock), no matter how the function under test is imported.