我可以"假的" python中的一个包(或至少一个模块)用于测试目的?

时间:2011-02-25 20:49:34

标签: python unit-testing mocking

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

from somefakepackage.morefakestuff import somethingfake

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

谢谢!

5 个答案:

答案 0 :(得分:28)

不确定。定义一个类,将所需的东西放在其中,将类分配给sys.modules["classname"]

class fakemodule(object):

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

import sys
sys.modules["package.module"] = fakemodule

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

import fakemodule, sys

sys.modules["package.module"] = fakemodule

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

注意,在这个例子中,它使用了一个实际的模块(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"))

现在可以运行其他脚本:

import fake_module

答案 2 :(得分:4)

您可以使用类似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 = ...

答案 3 :(得分:4)

我从其他答案中获取了一些想法并将它们变成了一个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

装饰器的代码如下

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_

可以找到项目intention。特别是,我为编程竞赛创建了这个,只允许参赛者提交一个.py文件。这允许我们开发一个包含多个.py文件的项目,然后将它们合并到一个.py文件中。

答案 4 :(得分:0)

TL;DR

使用 sys.modules 修补 unittest.mock

mock.patch.dict(
    sys.modules,
    {'somefakepackage': mock.Mock()},
)

说明

其他答案正确建议修复 sys.modules,但正确的方法是使用 mock.patch 修补它。意思是将它暂时(仅在测试运行时)替换为可选地模仿所需行为的假对象。并且在测试完成后恢复它不影响其他测试用例

TL;DR 部分中的代码只会使您丢失的包不会引发 ImportError。要提供包含内容的假包并模仿所需的行为,请使用适当的参数(例如 add attributes via Mock's **kwargs)启动 mock.Mock(…)

完整代码示例

下面的代码临时修补 sys.modules,使其包含 somefakepackage 并使其可从不带 ImportError 的依赖模块导入。

import sys
import unittest
from unittest import mock

class SomeTestCase(unittest.TestCase):
    def test_smth(self):
        # implement your testing logic, for example:
        self.assertEqual(
            123,
            somefakepackage_dependent.some_func(),
        )

    @classmethod
    def setUpClass(cls):  # called once before all the tests
        # define what to patch sys.modules with
        cls._modules_patcher = mock.patch.dict(
            sys.modules,
            {'somefakepackage': mock.Mock()},
        )
        # actually patch it
        cls._modules_patcher.start()
        # make the package globally visible and import it,
        #   just like if you have imported it in a usual way
        #   placing import statement at the top of the file,
        #   but relying on a patched dependency
        global somefakepackage_dependent
        import somefakepackage_dependent

    @classmethod  # called once after all tests
    def tearDownClass(cls):
        # restore initial sys.modules state back
        cls._modules_patcher.stop()

要详细了解 setUpClass/tearDownClass 方法,请see unittest docs

unittest 的内置mock 子包实际上是一个非常强大的工具。更好的 dive deeper into its documentation 以获得更好的理解。