2017-09-09 66 views
0

假设我有一个模块中定义的函数:如何嘲笑被测试模块直接导入功能,而不在Python知道模块的名称

module_a.py

def foo(): 
    return 10 

而且我想创建一个API来修补功能:

patcher.py

import mock 

class Patcher(object): 

    def __enter__(self): 
     self.patcher = mock.patch('module_a.foo', 
            mock.Mock(return_value=15)) 

     self.patcher.start() 

    def __exit__(self, *args): 
     self.patcher.stop() 

事情是,我不知道将使用我的API模块的名称是什么。这样的测试看起来像这样:

test1.py

from patcher import Patcher 
import module_a 

with Patcher(): 
    assert module_a.foo() == 15 

会工作。但是这样写的一个测试:

test2.py

from patcher import Patcher 
from module_a import foo 

with Patcher(): 
    assert foo() == 15 

将失败。

反正没有让API用户像第一个选项那样编写测试和模块(!)吗?

+0

你不打补丁的功能,他们是*定义*,你补一补,他们正在使用* *。阅读[mock'文档](https://docs.python.org/3/library/unittest.mock.html)。 – jonrsharpe

+0

为这个“未知模块”编写测试的开发人员将知道如何应用模拟补丁。为这些模块编写测试*不是你的工作*。 –

+0

你为什么首先写修补程序? –

回答

1

有一种方法可以在不知道修补程序发生的位置的情况下对功能进行“修补”。这是我的问题的要求,因为patcher是我的库API,并且我不想使用我的库为每个测试模块提供路径。

我发现的解决方案是传递所有已加载的模块,并尝试在其中找到foo,然后更改它 - 自己排列一个实现补丁。如果只有在启动Patcher后才会导入导入,我自己加载了该模块,并对其进行了更改。

现在的代码如下所示:

修补程式

import sys 
import mock 

from module_a import foo as _orig_foo 

import module_a 

class Patcher(object): 

    def __init__(self): 
     self.undo_set = set() 
     self.fake_foo = mock.Mock(return_value=15) 

    def __enter__(self): 
     modules = [ 
      module for mod_name, module in sys.modules.items() if 
      mod_name is not None and module is not None and 
      hasattr(module, '__name__') and 
      module.__name__ not in ('module_a', 'patcher') 
     ] 

     for module in modules: 
      for attr in dir(module): 
       try: 
        attribute_value = getattr(module, attr) 
       except (ValueError, AttributeError, ImportError): 
        # For some libraries, this happen. 
        continue 

       if id(attribute_value) == id(_orig_foo): 
        setattr(module, attr, self.fake_foo) 
        self.undo_set.add((module, attr, attribute_value)) 

     # Solve for future imports 
     module_a.foo = self.fake_foo 


    def __exit__(self, *args): 
     module_a.foo = _orig_foo 
     for mod, attr, val in self.undo_set: 
      setattr(mod, attr, val) 
     self.undo_set = set() 
相关问题