为了测试目的,我可以在python中"伪造"一个包(或至少一个模块)吗?

Weh*_*olt 17 python unit-testing mocking

我想在python中伪造一个包.我想定义一些代码可以做的事情

from somefakepackage.morefakestuff import somethingfake
Run Code Online (Sandbox Code Playgroud)

并且somefakepackage在代码中定义,因此它下面的所有内容都是如此.那可能吗?这样做的原因是欺骗我的单元测试我得到了一个包(或者我在标题中说的一个模块)在python路径中实际上只是为这个单元测试模拟的东西.

谢谢!

kin*_*all 30

当然.定义一个类,将所需内容放入其中,将类分配给sys.modules["classname"].

class fakemodule(object):

    @staticmethod
    def method(a, b):
        return a+b

import sys
sys.modules["package.module"] = fakemodule
Run Code Online (Sandbox Code Playgroud)

您还可以使用单独的模块(称之为fakemodule.py):

import fakemodule, sys

sys.modules["package.module"] = fakemodule
Run Code Online (Sandbox Code Playgroud)

  • 这样做的一个技巧是确保为`package`插入一个伪模块,其中`__path__ = []`作为顶级属性.没有它,模块层次结构是不完整的,这可能会混淆某些工具. (5认同)
  • 只是添加。如果要导入子模块,例如“ import abc”,则可能需要将伪造的模块分配给sys.modules [“ a”]`,sys.modules [“ ab”]`和sys.modules [“ abc”]` (2认同)

ide*_*n42 13

是的,你可以制作假模块:

from types import ModuleType
m = ModuleType("fake_module")

import sys
sys.modules[m.__name__] = m

# some scripts may expect a file
# even though this file doesn't exist,
# it may be used by Python for in error messages or introspection.
m.__file__ = m.__name__ + ".py"

# Add a function
def my_function():
    return 10

m.my_function = my_function
Run Code Online (Sandbox Code Playgroud)

注意,在这个例子中它使用了一个实际的模块,ModuleType因为一些Python代码可能需要模块,而不是虚拟类.

这可以做成一个效用函数:

def new_module(name, doc=None):
    import sys
    from types import ModuleType
    m = ModuleType(name, doc)
    m.__file__ = name + '.py'
    sys.modules[name] = m
    return m

print(new_module("fake_module", doc="doc string"))
Run Code Online (Sandbox Code Playgroud)

现在其他脚本可以运行:

import fake_module
Run Code Online (Sandbox Code Playgroud)


小智 7

我从其他答案中汲取了一些想法,并将它们转换为 Python 装饰器@modulize,将函数转换为模块。然后可以像往常一样导入该模块。这是一个例子。

@modulize('my_module')
def my_dummy_function(__name__):  # the function takes one parameter __name__
    # put module code here
    def my_function(s): 
        print(s, 'bar')

    # the function must return locals()
    return locals()

# import the module as usual
from my_module import my_function
my_function('foo') # foo bar
Run Code Online (Sandbox Code Playgroud)

装饰器的代码如下

import sys
from types import ModuleType

class MockModule(ModuleType):
    def __init__(self, module_name, module_doc=None):
        ModuleType.__init__(self, module_name, module_doc)
        if '.' in module_name:
            package, module = module_name.rsplit('.', 1)
            get_mock_module(package).__path__ = []
            setattr(get_mock_module(package), module, self)

    def _initialize_(self, module_code):
        self.__dict__.update(module_code(self.__name__))
        self.__doc__ = module_code.__doc__

def get_mock_module(module_name):
    if module_name not in sys.modules:
        sys.modules[module_name] = MockModule(module_name)
    return sys.modules[module_name]

def modulize(module_name, dependencies=[]):
    for d in dependencies: get_mock_module(d)
    return get_mock_module(module_name)._initialize_
Run Code Online (Sandbox Code Playgroud)

该项目可以在 GitHub 上找到。特别是,我为编程比赛创建了这个,它只允许参赛者提交一个.py文件。这允许开发一个包含多个 .py 文件的项目,然后最后将它们组合成一个.py文件。


Ant*_*lov 6

长话短说

\n

补丁sys.modules使用unittest.mock

\n
mock.patch.dict(\n    sys.modules,\n    {\'somefakepackage\': mock.Mock()},\n)\n
Run Code Online (Sandbox Code Playgroud)\n

解释

\n

其他答案正确地建议修复sys.modules,但正确的方法是使用临时修补mock.patch。仅当使用模仿所需行为的假对象运行测试时,它才会替换模块。并在测试完成后将其恢复,以免影响其他测试用例

\n

TL;DR 部分中的代码只会使您丢失的包不会引发ImportError。要提供假包的内容并模仿所需的行为,请mock.Mock(\xe2\x80\xa6)使用适当的参数启动(例如通过 Mock\'s 添加属性**kwargs)。

\n

完整代码示例

\n

下面的代码临时修补sys.modules,以便它包含somefakepackage并使其可以从不带ImportError.

\n
import sys\nimport unittest\nfrom unittest import mock\n\nclass SomeTestCase(unittest.TestCase):\n    def test_smth(self):\n        # implement your testing logic, for example:\n        self.assertEqual(\n            123,\n            somefakepackage_dependent.some_func(),\n        )\n\n    @classmethod\n    def setUpClass(cls):  # called once before all the tests\n        # define what to patch sys.modules with\n        cls._modules_patcher = mock.patch.dict(\n            sys.modules,\n            {\'somefakepackage\': mock.Mock()},\n        )\n        # actually patch it\n        cls._modules_patcher.start()\n        # make the package globally visible and import it,\n        #   just like if you have imported it in a usual way\n        #   placing import statement at the top of the file,\n        #   but relying on a patched dependency\n        global somefakepackage_dependent\n        import somefakepackage_dependent\n\n    @classmethod  # called once after all tests\n    def tearDownClass(cls):\n        # restore initial sys.modules state back\n        cls._modules_patcher.stop()\n\n
Run Code Online (Sandbox Code Playgroud)\n

要了解有关setUpClass/tearDownClass方法的更多信息,请参阅unittestdocs

\n

unittest\的内置mock分包实际上是一个非常强大的工具。深入研究其文档以获得更好的理解。

\n


Obe*_*nne 5

你可以用一个行为像这样的类来伪造它somethingfake

try:
    from somefakepackage.morefakestuff import somethingfake
except ImportError:
    class somethingfake(object):
         # define what you'd expect of somethingfake, e.g.:
         @staticmethod
         def somefunc():
             ...
         somefield = ...
Run Code Online (Sandbox Code Playgroud)