代码之家  ›  专栏  ›  技术社区  ›  Attila O.

在python中模拟导入错误

  •  6
  • Attila O.  · 技术社区  · 16 年前

    我已经试了将近两个小时了,没有任何运气。

    我有一个类似这样的模块:

    try:
        from zope.component import queryUtility  # and things like this
    except ImportError:
        # do some fallback operations <-- how to test this?
    

    代码后面部分:

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

    有什么想法吗?

    编辑:

    亚历克斯的建议似乎行不通:

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

    运行测试时:

    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
    

    但是,它 当我从Python交互控制台运行相同的代码时工作。

    更多编辑:

    我在用 zope.testing 一份测试文件, shorturl.txt 所有的测试都是针对我的模块的这一部分的。首先,我要导入模块 zope.component 可用于演示和测试常用用法。缺少 zope.* 包被认为是边缘情况,所以稍后我将进行测试。因此,我必须 reload() 我的模块,制作后 Zop. * 不可用,不知何故。

    到目前为止,我甚至尝试使用 tempfile.mktempdir() 而且是空的 zope/__init__.py zope/component/__init__.py tempdir中的文件,然后将tempdir插入到 sys.path[0] 和移除旧的 Zop. * 来自的包 sys.modules .

    也没用。

    更多编辑:

    在此期间,我尝试过:

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

    对于测试套件的名称空间(=中的所有导入 短网址.txt ,但它没有在我的主模块中执行, ao.shorturl . 即使当我 重新加载() 它。知道为什么吗?

    >>> import zope  # ok, this raises an ImportError
    >>> reload(ao.shorturl)    <module ...>
    

    导入 zope.interfaces 提高 ImportError ,所以它不能到达我导入的部分 Zope.组件 它保留在ao.shorturl名称空间中 . 为什么?!

    >>> ao.shorturl.zope.component  # why?! 
    <module ...>
    
    3 回复  |  直到 9 年前
        1
  •  9
  •   Błażej Michalik Arkaprova Majumder    9 年前

    只需将Monkeypatch插入 builtins 您自己的版本 __import__ --当它识别出你想要模拟错误的特定模块正在调用它时,它可以提升你想要的任何东西。见 the docs 为了丰富的细节。大致上:

    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
    

    代替 ... ,你可以硬编码 name == 'zope.component' 或者通过自己的回调更灵活地安排事情,根据您的特定测试需求,可以在不同情况下根据需要增加导入,而无需编写多个代码。 _导入__ -相似函数;-)。

    还要注意,如果您使用的是 import zope.component from zope.component import something from zope import component , the name 然后会是 'zope' 'component' 将是 fromlist .

    编辑 :文档 _导入__ 函数表示要导入的名称是 builtin (就像在Python3中一样),但实际上您需要 __builtins__ --我已经编辑了上面的代码,这样它就可以以任何方式工作。

        2
  •  3
  •   Scott Robinson    16 年前

    这是我在单元测试中提出的。

    它使用 PEP-302 "New Import Hooks" . (警告:PEP-302文件和我链接的更简洁的发行说明并不完全正确 精确的 )

    我用 meta_path 因为它在导入序列中尽可能早。

    如果模块已经被导入(在我的例子中,因为前面的UnitTests对其进行了模拟),那么在执行 reload 在相关模块上。

    Ensure we fallback to using ~/.pif if XDG doesn't exist.
    
     >>> import sys
    
     >>> class _():
     ... def __init__(self, modules):
     ...  self.modules = modules
     ...
     ...  def find_module(self, fullname, path=None):
     ...  if fullname in self.modules:
     ...   raise ImportError('Debug import failure for %s' % fullname)
    
     >>> fail_loader = _(['xdg.BaseDirectory'])
     >>> sys.meta_path.append(fail_loader)
    
     >>> del sys.modules['xdg.BaseDirectory']
    
     >>> reload(pif.index) #doctest: +ELLIPSIS
     <module 'pif.index' from '...'>
    
     >>> pif.index.CONFIG_DIR == os.path.expanduser('~/.pif')
     True
    
     >>> sys.meta_path.remove(fail_loader)
    

    其中pif.index中的代码如下:

    try:
        import xdg.BaseDirectory
    
        CONFIG_DIR = os.path.join(xdg.BaseDirectory.xdg_data_home, 'pif')
    except ImportError:
        CONFIG_DIR = os.path.expanduser('~/.pif')
    

    为了回答为什么新加载的模块具有旧加载和新加载的属性的问题,这里有两个示例文件。

    第一个是模块 y 在导入失败的情况下。

    # y.py
    
    try:
        import sys
    
        _loaded_with = 'sys'
    except ImportError:
        import os
    
        _loaded_with = 'os'
    

    第二个是 x 它演示了在重新加载模块时,将句柄留给模块会如何影响其属性。

    # x.py
    
    import sys
    
    import y
    
    assert y._loaded_with == 'sys'
    assert y.sys
    
    class _():
        def __init__(self, modules):
            self.modules = modules
    
        def find_module(self, fullname, path=None):
            if fullname in self.modules:
                raise ImportError('Debug import failure for %s' % fullname)
    
    # Importing sys will not raise an ImportError.
    fail_loader = _(['sys'])
    sys.meta_path.append(fail_loader)
    
    # Demonstrate that reloading doesn't work if the module is already in the
    # cache.
    
    reload(y)
    
    assert y._loaded_with == 'sys'
    assert y.sys
    
    # Now we remove sys from the modules cache, and try again.
    del sys.modules['sys']
    
    reload(y)
    
    assert y._loaded_with == 'os'
    assert y.sys
    assert y.os
    
    # Now we remove the handles to the old y so it can get garbage-collected.
    del sys.modules['y']
    del y
    
    import y
    
    assert y._loaded_with == 'os'
    try:
        assert y.sys
    except AttributeError:
        pass
    assert y.os
    
        3
  •  0
  •   blais    11 年前

    如果不介意更改程序本身,也可以将import调用放入函数中,并在测试中对其进行修补。

    推荐文章