2010-03-20 51 views
6

Tôi đang cố gắng này trong gần hai giờ, không may mắn.Nhập khẩu Mocking bằng Python

Tôi có một mô-đun đó trông như thế này:

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

Sau đó trong các mã:

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

Bất kỳ ý tưởng?

EDIT:

đề nghị của Alex dường như không làm việc:

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

Khi chạy các bài kiểm tra:

[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 

Tuy nhiên, nó không làm việc khi tôi chạy cùng một mã từ giao diện điều khiển tương tác python. EDIT

THÊM:

Tôi đang sử dụng zope.testing và một tập tin kiểm tra, shorturl.txt có tất cả các xét nghiệm cụ thể cho phần này của mô-đun của tôi. Trước tiên, tôi sẽ nhập mô-đun với zope.component có sẵn để chứng minh & kiểm tra mức sử dụng thông thường. Sự vắng mặt của zope.* gói được coi là một trường hợp cạnh, vì vậy tôi đang thử nghiệm nó sau này. Vì vậy, tôi phải reload() mô-đun của mình, sau khi thực hiện zope.* không có sẵn, bằng cách nào đó.

Cho đến nay tôi đã thậm chí đã cố gắng sử dụng tempfile.mktempdir() và trống rỗng zope/__init__.pyzope/component/__init__.py file trong tempdir, sau đó chèn tempdir để sys.path[0], và loại bỏ các zope.* gói cũ từ sys.modules.

Không hoạt động.

NGAY CẢ EDIT THÊM:

Trong khi đó, tôi đã cố gắng này:

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

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

Và nó hoạt động tốt cho không gian tên của bộ ứng dụng thử nghiệm (= cho tất cả các hàng nhập khẩu trong shorturl.txt) , nhưng nó không được thực hiện trong mô-đun chính của tôi, ao.shorturl. Ngay cả khi tôi reload() nó. Bất kỳ ý tưởng tại sao?

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

Nhập zope.interfaces đặt ra một ImportError, vì vậy nó không đến được với các phần nơi tôi nhập zope.component, và nó vẫn còn trong không gian tên ao.shorturl. Tại sao?!

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

Trả lời

9

Chỉ monkeypatch vào builtins phiên bản của riêng bạn __import__ - nó có thể tăng bất cứ điều gì bạn muốn khi nó nhận ra nó đang được kêu gọi các module cụ thể mà bạn muốn giả lập lỗi. Xem the docs để biết chi tiết phong phú.Khoảng:

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 

Thay của ..., bạn có thể hardcode name == 'zope.component', hoặc sắp xếp mọi thứ một cách linh hoạt hơn với một callback của riêng bạn mà có thể làm cho nhập khẩu tăng theo yêu cầu trong các trường hợp khác nhau, tùy thuộc vào nhu cầu thử nghiệm cụ thể của bạn, mà không yêu cầu bạn mã nhiều __import__ các hàm tương tự ;-).

Cũng lưu ý rằng nếu những gì bạn sử dụng, thay vì import zope.component hoặc from zope.component import something, là from zope import component, các name sau đó sẽ được 'zope', và 'component' sau đó sẽ là mục duy nhất trong fromlist.

Sửa: các tài liệu cho __import__ chức năng nói rằng tên để nhập khẩu là builtin (như trong Python 3), nhưng trong thực tế, bạn cần __builtins__ - Tôi đã chỉnh sửa đoạn mã trên để nó hoạt động một trong hai cách .

+0

Ah, cảm ơn! Vì một lý do nào đó, tôi đã cố gắng thực hiện 'def __import __()', nhưng không gán nó cho 'builtin .__ import__'; tôi ngớ ngẩn quá. Thú vị, tôi chỉ đọc câu trả lời của bạn ở đây: http://stackoverflow.com/questions/2216828/mock-y-of-from-x-import-y-in-doctest-python/2217512#2217512 - bạn có nghĩ nó sẽ làm cho tình huống này trở nên dễ dàng hơn nếu tôi không import queryUtility vào phạm vi module của tôi? –

+2

@Attila, nếu bạn đã làm 'từ thành phần nhập khẩu zope' và sau đó sử dụng' component.queryUtility', nó sẽ làm cho nó dễ dàng hơn, ví dụ, để sử dụng thực tế một số thời gian, và một phiên bản giả/giả tại lần khác.Như tôi đã viết trong câu trả lời đó, tôi đề nghị nó như là một điều chung, và đó là một phần của cách chúng ta viết mã Python tại Google (đôi khi mệnh đề 'as' để rút ngắn tên một hàng nhập khẩu được bảo đảm, tất nhiên, nhưng điều đó không ' t thay đổi ngữ nghĩa). –

+1

Nếu bạn thực hiện 'từ thành phần import zope', BTW, hàm' __import__' của bạn sẽ thấy ''zope'' làm đối số' name' và '' component'' như một mục trong đối số' fromlist' (chỉ có một, trừ khi bạn làm 'từ zope nhập khẩu này, đó, component' hoặc tương tự ;-); vì vậy hãy chắc chắn để kích hoạt cho phù hợp. –

3

Đây là những gì tôi đã chỉ ra trong các lần hủy đăng ký của mình.

Sử dụng PEP-302 "New Import Hooks". (Cảnh báo: tài liệu PEP-302 và các ghi chú phát hành ngắn gọn hơn mà tôi liên kết không chính xác là chính xác.)

Tôi sử dụng meta_path vì nó càng sớm càng tốt trong chuỗi nhập.

Nếu mô-đun đã được nhập (như trong trường hợp của tôi, vì trước đó bỏ qua mô hình chống lại nó), thì cần phải xóa nó khỏi sys.modules trước khi thực hiện reload trên mô đun phụ thuộc.

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) 

Trường hợp mã bên pif.index trông giống như:

try: 
    import xdg.BaseDirectory 

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

Để trả lời câu hỏi về việc tại sao các module mới được nạp lại có các tính chất của tải cũ và mới, đây là hai ví dụ các tập tin.

Đầu tiên là mô-đun y với trường hợp lỗi nhập.

# y.py 

try: 
    import sys 

    _loaded_with = 'sys' 
except ImportError: 
    import os 

    _loaded_with = 'os' 

Thứ hai là x minh họa cách xử lý mô-đun có thể ảnh hưởng đến các thuộc tính của nó khi tải lại.

# 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

Tuyệt vời, bây giờ tôi quản lý để nâng cao một 'ImportError', đó là tất cả những gì tôi cần. Điều thú vị là: nếu tôi tải lại 'ao.shorturl', và trong đó tôi có' try: import zope.component, zope.interface; ngoại trừ ImportError: fallback() ', và tôi nhận được' ImportError' đầu tiên cho 'zope.component', ** zope.interface sẽ vẫn có sẵn trong ao.shorturl (ao.shorturl.zope.interface) **. Tại sao vậy? –

+0

Tôi vừa thêm một phần nữa mô tả lý do tại sao điều đó xảy ra. tl; dr, bạn cần 'del ao.shorturl' trước khi' tải lại'. –

0

Nếu bạn không ngại thay đổi chương trình, bạn cũng có thể thực hiện cuộc gọi nhập bằng chức năng và bản vá trong thử nghiệm của mình.

Các vấn đề liên quan