Python:模拟一个模块而不导入它或需要它存在

Kyl*_*son 12 python import module mocking

我开始使用python 模拟库进行测试.我想模拟一个在被测模块的命名空间中导入的模块,而不是实际导入它或要求它首先存在(即抛出一个ImportError).

假设存在以下代码:

foo.py

 import helpers
 def foo_func():
    return helpers.helper_func()
Run Code Online (Sandbox Code Playgroud)

目标是在'helpers.py'不存在于任何地方时测试foo_func(),如果确实存在,则表现为不行.

首先尝试使用超酷的@patch装饰器:

from mock import patch, sentinel
import foo
@patch("foo.helpers")
def foo_test(mock):
    mock.helper_func.return_value = sentinel.foobar
    assert foo.foo_func() == sentinel.foobar
Run Code Online (Sandbox Code Playgroud)

如果可以导入"帮助程序"模块,则此方法有效.如果它不存在,我会得到一个ImportError.

下一次尝试补丁,没有装饰者:

from mock import patch, sentinel, Mock
import foo
helpers_mock = patch("foo.helpers")
helpers_mock.start()

def foo_test():
    helpers_mock.helper_func = Mock('helper_func')
    helpers_mock.helper_func.return_value = sentinel.foobar
    assert foo.foo_func() == sentinel.foobar
Run Code Online (Sandbox Code Playgroud)

同样,如果缺少"助手",这不起作用......如果存在,则断言失败.不确定为什么会这样.

第三次尝试,当前解决方案

import sys
helpers_mock = Mock(name="helpers_mock", spec=['helper_func'])
helpers_mock.__name__ = 'helpers'
sys.modules['helpers'] = helpers_mock
import foo
def foo_test():
    helpers_mock.helper_func.return_value = sentinel.foobar
    assert foo.foo_func() == sentinel.foobar
Run Code Online (Sandbox Code Playgroud)

无论是否存在"helpers.py",此测试都会通过.

这是实现这一目标的最佳方式吗?我使用的模拟库是否提供了替代方案?我还有什么方法可以做到这一点?

tan*_*orm 6

你有点忽略了 Mock 是什么。当您想要一个具有特定接口的对象时,您应该构建它们,而不管它是如何实现的。

你正在做的是试图重新实现 python 的模块系统,而且这是一个非常可怕的全局变量滥用来启动。

不是让 foo 成为一个模块,而是创建一个 Foo 类,并在构造函数中传入帮助器。

class Foo(object):
    def __init__(self, helpers):
        self.helpers = helpers

# then, instead of import foo:
foo = Foo(mock_helpers)
Run Code Online (Sandbox Code Playgroud)

即使真正的“助手”实际上是一个模块,您也没有理由需要在测试中弄乱 sys.modules 并通过它进行设置import

如果 foo 必须是一个模块,那也很好,但是你可以这样做:

# foo.py
class Foo(object):
    pass # same code as before, plus foo_func

try:
   import whatever
   _singleton = Foo(whatever)
except ImportError:
   _singleton = Foo(something_else)

def foo_func():
   return _singleton.foo_func()
Run Code Online (Sandbox Code Playgroud)

标准库的大块以这种方式工作。它几乎是定义类似单例的模块的标准。

  • 这是非常好的信息,不幸的是,这意味着要重写大量代码,目前这并不实用。但是对于新项目,我肯定会尝试这种方法。 (3认同)