2010-03-20 6 views
6

나는 이것을 거의 2 시간 동안 아무런 노력없이 시도하고 있습니다.Python에서 ImportError를 조롱하십시오.

나중에 코드에서
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 

어떤 아이디어

나는 다음과 같다 모듈이 있나요?

편집 :

알렉스의 제안이 작동하지 않는 것 : 나는 실행할 때 가하는, 일을하지만

[email protected] ~/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 

:

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

이 테스트를 실행하는 경우 파이썬 대화 형 콘솔에서 동일한 코드.

더 편집 : 나는 zope.testing을 사용하고

내 모듈의이 부분에 고유 한 모든 테스트가 테스트 파일, shorturl.txt. 먼저 zope.component 모듈을 가져 와서 &의 일반적인 사용법을 테스트합니다. zope.* 패키지가 없으면 가장자리 케이스로 간주되므로 나중에 테스트 할 것입니다. 따라서, 내 모듈 zope.*을 사용할 수 없게 된 후, 어떻게 든.

지금까지 난 다음 sys.path[0]에 TEMPDIR를 삽입하고, sys.modules에서 이전 zope.* 패키지를 제거의 TEMPDIR에 tempfile.mktempdir()zope/__init__.pyzope/component/__init__.py 파일을 사용하려고했습니다.

작동하지 않았습니다.

더 편집 : 한편

, 나는이 시도했다 :

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

>>> import sys 
>>> sys.path.insert(0, NoZope()) 

을 그리고 그것은 테스트 스위트의 네임 스페이스에 대해 잘 작동 (= shorturl.txt의 모든 수입) ,하지만 내 메인 모듈에서 실행되지 않습니다 ao.shorturl. 심지어 내가 reload() 일 때. 왜 그런가? zope.interfaces 가져 오기

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

ImportError을 제기, 그래서 내가 zope.component를 가져올 부분에 도착하지 않으며, 그것은 ao.shorturl 네임 스페이스에 남아 있습니다. 왜?!

>>> ao.shorturl.zope.component # why?! 
<module ...> 

답변

9

그냥 __import__builtins 자신의 버전으로 monkeypatch - 그것은 그것은 당신이 오류를 조롱하려는 특정 모듈에 호출되는 것 인식하면 당신이 원하는대로 올릴 수 있습니다. 자세한 내용은 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__ - 모두 기능을 코딩해야합니다. ;-). 당신이 대신 import zope.component 또는 from zope.component import something의 사용 무엇 from zope import component 인 경우

참고 또한 name'zope' 될 것, 그리고 'component' 다음 fromlist의 유일한 항목 될 것입니다.

편집 다음 __import__ 기능에 대한 문서 가져 오기에 이름 (파이썬 3에서와 같은) builtin이라고 말하지만, 사실 당신은 __builtins__ 필요 - 그것은 어느 쪽이든 작동하도록 내가 위의 코드를 편집 한 .

+0

아, 고마워! 웬일인지'def __import __()'를 시도했지만'builtin .__ import__'에 할당하지 않았습니다; 바보 나. 흥미 롭 군, 난 그냥 여기에 귀하의 답변을 읽고 : http://stackoverflow.com/questions/2216828/mock-y-of-from-x-import-y-in-doctest-python/2217512#2217512 - 생각하십니까? 내 모듈의 범위에 queryUtility를 가져 오지 않으면이 상황이 더 쉬울까요? –

+2

@Attila, 만약'zope import component'에서'component.queryUtility'를 사용했다면, 예를 들어 진짜 물건을 사용하고, 조롱 한/가짜 버전을 좀 더 쉽게 만들 수 있습니다. 다른 시간.나는 그 대답을 썼다. 나는 그것을 일반적인 것으로 추천한다. 그리고 그것은 구글에서 파이썬을 코딩하는 방식의 일부이다. (때로는 하나의 수입품 이름을 줄이기 위해 'as' 조항을 사용하기도한다.하지만 그것은' t 의미 변경). –

+1

'from zope import component', BTW,'__import__'와 같은 함수는'name' 인자로'zope'을,'fromlist' 인자에는' 당신이'zope import from this, that, component' 등을하지 않는 한 단 하나뿐입니다 ;-); 그에 따라 트리거해야합니다. –

3

이것은 내가 단원 테스트에서 정당화 한 것입니다.

PEP-302 "New Import Hooks"을 사용합니다. (경고 : PEP-302 문서와 내가 연결되어 더 간결 릴리스 노트 정확히 정확한하지 않습니다.)

는 가능한 한 빨리 가져 오기 순서이기 때문에이 meta_path를 사용합니다.

모듈을 이미 가져온 경우 (이전의 unittest가 모의했기 때문에 제 경우와 같이) 종속 모듈에서 reload을 수행하기 전에 sys.modules에서 해당 모듈을 제거해야합니다. pif.index 내부의 코드처럼 보이는

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) 

:

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 
+0

좋아, 이제는 내가 필요한 모든 것 인'ImportError'를 제기했다. 흥미로운 점은 이것입니다 :'ao.shorturl'을 새로 고치면 그 안에'try zope.component, zope.interface; ImportError : fallback()'을 제외하고,'zope.component'에 대한 첫 번째'ImportError'를 얻습니다. ** zope.interface는 여전히 ao.shorturl (ao.shorturl.zope.interface) **에서 사용할 수 있습니다. 왜 그런가요? –

+0

그 이유를 설명하는 추가 섹션을 추가했습니다. dr, 당신은'reload' 전에'del ao.shorturl' 할 필요가 있습니다. –

0

프로그램 자체를 변경할 필요가 없다면 가져 오기 호출을 테스트에 포함 된 함수 및 패치에 넣을 수도 있습니다.