在Python中模拟ImportError

Att*_* O. 7 python doctest unit-testing mocking zope.component

我现在正在尝试这个近两个小时,没有任何运气.

我有一个看起来像这样的模块:

try:
    from zope.component import queryUtility  # and things like this
except ImportError:
    # do some fallback operations <-- how to test this?
Run Code Online (Sandbox Code Playgroud)

稍后在代码中:

try:
    queryUtility(foo)
except NameError:
    # do some fallback actions <-- this one is easy with mocking 
    # zope.component.queryUtility to raise a NameError
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

编辑:

亚历克斯的建议似乎不起作用:

>>> import __builtin__
>>> realimport = __builtin__.__import__
>>> def fakeimport(name, *args, **kw):
...     if name == 'zope.component':
...         raise ImportError
...     realimport(name, *args, **kw)
...
>>> __builtin__.__import__ = fakeimport
Run Code Online (Sandbox Code Playgroud)

运行测试时:

aatiis@aiur ~/work/ao.shorturl $ ./bin/test --coverage .
Running zope.testing.testrunner.layer.UnitTests tests:
  Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds.


Error in test /home/aatiis/work/ao.shorturl/src/ao/shorturl/shorturl.txt
Traceback (most recent call last):
  File "/usr/lib64/python2.5/unittest.py", line 260, in run
    testMethod()
  File "/usr/lib64/python2.5/doctest.py", line 2123, in runTest
    test, out=new.write, clear_globs=False)
  File "/usr/lib64/python2.5/doctest.py", line 1361, in run
    return self.__run(test, compileflags, out)
  File "/usr/lib64/python2.5/doctest.py", line 1282, in __run
    exc_info)
  File "/usr/lib64/python2.5/doctest.py", line 1148, in report_unexpected_exception
    'Exception raised:\n' + _indent(_exception_traceback(exc_info)))
  File "/usr/lib64/python2.5/doctest.py", line 1163, in _failure_header
    out.append(_indent(source))
  File "/usr/lib64/python2.5/doctest.py", line 224, in _indent
    return re.sub('(?m)^(?!$)', indent*' ', s)
  File "/usr/lib64/python2.5/re.py", line 150, in sub
    return _compile(pattern, 0).sub(repl, string, count)
  File "/usr/lib64/python2.5/re.py", line 239, in _compile
    p = sre_compile.compile(pattern, flags)
  File "/usr/lib64/python2.5/sre_compile.py", line 507, in compile
    p = sre_parse.parse(p, flags)
AttributeError: 'NoneType' object has no attribute 'parse'



Error in test BaseShortUrlHandler (ao.shorturl)
Traceback (most recent call last):
  File "/usr/lib64/python2.5/unittest.py", line 260, in run
    testMethod()
  File "/usr/lib64/python2.5/doctest.py", line 2123, in runTest
    test, out=new.write, clear_globs=False)
  File "/usr/lib64/python2.5/doctest.py", line 1351, in run
    self.debugger = _OutputRedirectingPdb(save_stdout)
  File "/usr/lib64/python2.5/doctest.py", line 324, in __init__
    pdb.Pdb.__init__(self, stdout=out)
  File "/usr/lib64/python2.5/pdb.py", line 57, in __init__
    cmd.Cmd.__init__(self, completekey, stdin, stdout)
  File "/usr/lib64/python2.5/cmd.py", line 90, in __init__
    import sys
  File "<doctest shorturl.txt[10]>", line 4, in fakeimport
NameError: global name 'realimport' is not defined
Run Code Online (Sandbox Code Playgroud)

但是,当我从python交互式控制台运行相同的代码时,它确实有效.

更多编辑:

我正在使用zope.testing和测试文件,shorturl.txt它具有特定于我的模块的这一部分的所有测试.首先,我导入模块zope.component可用,以演示和测试通常的用法.没有zope.*包被认为是边缘情况,所以我稍后会测试它.因此,不知何故,我不得不在reload()我的模块之后zope.*.

到目前为止,我已经使用甚至试图tempfile.mktempdir()和空zope/__init__.pyzope/component/__init__.py在TEMPDIR文件,然后插入到TEMPDIR sys.path[0],并移除旧zope.*的包sys.modules.

也没用.

更多编辑:

与此同时,我试过这个:

>>> class NoZope(object):
...     def find_module(self, fullname, path):
...         if fullname.startswith('zope'):
...             raise ImportError
... 

>>> import sys
>>> sys.path.insert(0, NoZope())
Run Code Online (Sandbox Code Playgroud)

它适用于测试套件的命名空间(=所有导入shorturl.txt),但它不在我的主模块中执行ao.shorturl.甚至不是我的reload()时候.知道为什么吗?

>>> import zope  # ok, this raises an ImportError
>>> reload(ao.shorturl)    <module ...>
Run Code Online (Sandbox Code Playgroud)

导入zope.interfaces引发ImportError,所以也没有去的地方我导入部分zope.component,和它保持在ao.shorturl命名空间.为什么?!

>>> ao.shorturl.zope.component  # why?! 
<module ...>
Run Code Online (Sandbox Code Playgroud)

Ale*_*lli 10

只需monkeypatch进入builtins你自己的版本__import__- 当它识别出你想要模拟错误的特定模块时,它可以提出你想要的任何东西.有关详细信息,请参阅文档.大致:

try:
    import builtins
except ImportError:
    import __builtin__ as builtins
realimport = builtins.__import__

def myimport(name, globals, locals, fromlist, level):
    if ...:
        raise ImportError
    return realimport(name, globals, locals, fromlist, level)

builtins.__import__ = myimport
Run Code Online (Sandbox Code Playgroud)

取而代之的是...,您可以name == 'zope.component'使用自己的回调进行硬编码或更灵活地安排事情,这可以根据您的特定测试需求在不同情况下按需提升导入,而无需您编写多个__import__相似的功能;-) .

还要注意,如果您使用的是,而不是import zope.component或将from zope.component import somethingfrom zope import component,那么name将是'zope','component'然后将是唯一的项目fromlist.

编辑:该__import__函数的文档说要导入的名称builtin(如在Python 3中),但事实上你需要__builtins__- 我编辑了上面的代码,以便它可以以任何方式工作.

  • @Attila,如果你使用`zope import component`然后使用`component.queryUtility`,那么它会更容易,例如,在某些时候使用真实的东西,在其他时候使用模拟/伪造的版本倍.正如我在那个答案中写的那样,我确实推荐它作为一般性的东西,它是我们在谷歌编写Python的方式的一部分(有时候`as`条款缩短了一个进口的名称当然是有保证的,但那并不是'改变语义). (2认同)