Làm thế nào để giả định nhập khẩu


143

Mô-đun Abao gồm import Bở đầu của nó. Tuy nhiên trong điều kiện thử nghiệm tôi muốn thử B trong A(giả A.B) và hoàn toàn kiềm chế nhập khẩu B.

Trong thực tế, Bkhông được cài đặt trong môi trường thử nghiệm trên mục đích.

Alà đơn vị đang thử nghiệm. Tôi phải nhập khẩu Avới tất cả các chức năng của nó. Blà mô-đun tôi cần để giả. Nhưng làm thế nào tôi có thể chế giễu Bbên trong Avà ngừng Anhập hàng thật B, nếu việc đầu tiên Alàm là nhập khẩu B?

(Lý do B chưa được cài đặt là tôi sử dụng pypy để kiểm tra nhanh và không may B chưa tương thích với pypy.)

Làm thế nào điều này có thể được thực hiện?

Câu trả lời:


134

Bạn có thể chỉ định sys.modules['B']trước khi nhập Ađể có được những gì bạn muốn:

kiểm tra :

import sys
sys.modules['B'] = __import__('mock_B')
import A

print(A.B.__name__)

A.py :

import B

Lưu ý B.py không tồn tại, nhưng khi chạy test.pykhông có lỗi được trả lại và print(A.B.__name__)in mock_B. Bạn vẫn phải tạo một mock_B.pynơi mà bạn mô phỏng Bcác hàm / biến / thực tế của bạn . Hoặc bạn có thể chỉ định Mock()trực tiếp:

kiểm tra :

import sys
sys.modules['B'] = Mock()
import A

3
Hãy nhớ rằng Mocksẽ không vá một số thuộc tính ma thuật ( __%s__) như __name__.
reclosesev

7
@reclosesev - có Magic Mock cho điều đó
Jonathan

2
Làm thế nào để bạn hoàn tác việc này để nhập B lại là NhậpError? Tôi đã thử sys.modules['B'] = Nonenhưng nó dường như không hoạt động.
audiodude

2
Làm thế nào để bạn thiết lập lại quá trình nhập giả định này vào cuối thử nghiệm, để các tệp kiểm tra đơn vị khác không bị ảnh hưởng bởi đối tượng bị giả?
Riya John

1
Để rõ ràng, bạn nên chỉnh sửa câu trả lời để thực sự nhập mockvà sau đó gọimock.Mock()
nmz787

28

Nội dung __import__có thể được mô phỏng với thư viện 'giả' để kiểm soát nhiều hơn:

# Store original __import__
orig_import = __import__
# This will be the B module
b_mock = mock.Mock()

def import_mock(name, *args):
    if name == 'B':
        return b_mock
    return orig_import(name, *args)

with mock.patch('__builtin__.__import__', side_effect=import_mock):
    import A

Nói Agiống như:

import B

def a():
    return B.func()

A.a()trả lại b_mock.func()mà cũng có thể bị chế giễu.

b_mock.func.return_value = 'spam'
A.a()  # returns 'spam'

Lưu ý cho Python 3: Như đã nêu trong thay đổi cho 3.0 , __builtin__hiện được đặt tên builtins:

Đổi tên mô-đun __builtin__thành builtins(loại bỏ các dấu gạch dưới, thêm một 's').

Mã trong câu trả lời này hoạt động tốt nếu bạn thay thế __builtin__bằng builtinsPython 3.


1
Có ai xác nhận công trình này? Tôi đang thấy import_mockđược gọi cho import A, nhưng không phải cho bất cứ thứ gì nó nhập khẩu.
Jonathon Reinhart

3
Với Python 3.4.3 tôi nhận đượcImportError: No module named '__builtin__'
Lucas Cimon

bạn phải nhập__builtin__
Aidenhjj

1
@LucasCimon, thay thế __builtin__bằng builtinscho python3 ( docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__ )
Luke Marlin

17

Làm thế nào để giả định một nhập khẩu, (giả AB)?

Mô-đun A bao gồm nhập B ở đầu.

Dễ dàng, chỉ cần giả lập thư viện trong sys.modules trước khi được nhập:

if wrong_platform():
    sys.modules['B'] = mock.MagicMock()

và sau đó, miễn là Akhông phụ thuộc vào các loại dữ liệu cụ thể được trả về từ các đối tượng của B:

import A

chỉ nên làm việc

Bạn cũng có thể chế giễu import A.B:

Điều này hoạt động ngay cả khi bạn có các mô hình con, nhưng bạn sẽ muốn chế giễu từng mô-đun. Nói rằng bạn có điều này:

from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink

Để giả, chỉ cần thực hiện các thao tác bên dưới trước khi mô-đun chứa phần trên được nhập:

sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()

(Kinh nghiệm của tôi: Tôi đã có một sự phụ thuộc hoạt động trên một nền tảng, Windows, nhưng không hoạt động trên Linux, nơi chúng tôi chạy thử nghiệm hàng ngày. Vì vậy, tôi cần phải chế giễu sự phụ thuộc cho các thử nghiệm của mình. May mắn thay, đó là một hộp đen, vì vậy Tôi không cần phải thiết lập nhiều tương tác.)

Tác dụng phụ nhạo báng

Phụ lục: Thật ra, tôi cần mô phỏng một hiệu ứng phụ mất một thời gian. Vì vậy, tôi cần một phương pháp của một đối tượng để ngủ trong một giây. Điều đó sẽ làm việc như thế này:

sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
# setup the side-effect:
from time import sleep

def sleep_one(*args): 
    sleep(1)

# this gives us the mock objects that will be used
from foo.bar import MyObject 
my_instance = MyObject()
# mock the method!
my_instance.method_that_takes_time = mock.MagicMock(side_effect=sleep_one)

Và sau đó mã phải mất một thời gian để chạy, giống như phương thức thực.


7

Tôi nhận ra rằng tôi đến bữa tiệc muộn một chút, nhưng đây là một cách hơi điên rồ để tự động hóa việc này với mockthư viện:

(đây là một ví dụ sử dụng)

import contextlib
import collections
import mock
import sys

def fake_module(**args):
    return (collections.namedtuple('module', args.keys())(**args))

def get_patch_dict(dotted_module_path, module):
    patch_dict = {}
    module_splits = dotted_module_path.split('.')

    # Add our module to the patch dict
    patch_dict[dotted_module_path] = module

    # We add the rest of the fake modules in backwards
    while module_splits:
        # This adds the next level up into the patch dict which is a fake
        # module that points at the next level down
        patch_dict['.'.join(module_splits[:-1])] = fake_module(
            **{module_splits[-1]: patch_dict['.'.join(module_splits)]}
        )
        module_splits = module_splits[:-1]

    return patch_dict

with mock.patch.dict(
    sys.modules,
    get_patch_dict('herp.derp', fake_module(foo='bar'))
):
    import herp.derp
    # prints bar
    print herp.derp.foo

Lý do điều này rất phức tạp một cách nực cười là khi nhập khẩu xảy ra python về cơ bản thực hiện điều này (lấy ví dụ from herp.derp import foo)

  1. sys.modules['herp']tồn tại? Khác nhập nó. Nếu vẫn khôngImportError
  2. sys.modules['herp.derp']tồn tại? Khác nhập nó. Nếu vẫn khôngImportError
  3. Nhận thuộc tính foocủa sys.modules['herp.derp']. KhácImportError
  4. foo = sys.modules['herp.derp'].foo

Có một số nhược điểm của giải pháp hack cùng nhau này: Nếu một cái gì đó khác phụ thuộc vào các công cụ khác trong đường dẫn mô-đun loại vít này. Ngoài ra, điều này chỉ hoạt động cho những thứ đang được nhập nội tuyến, chẳng hạn như

def foo():
    import herp.derp

hoặc là

def foo():
    __import__('herp.derp')

6

Câu trả lời của Aaron Hall làm việc cho tôi. Chỉ muốn đề cập đến một điều quan trọng,

nếu trong A.pybạn làm

from B.C.D import E

sau đó trong test.pybạn phải chế nhạo mọi mô-đun dọc theo đường dẫn, nếu không bạn sẽ nhận đượcImportError

sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()

4

Tôi tìm thấy cách tốt để giả định nhập khẩu trong Python. Đó là giải pháp Zaadi của Eric được tìm thấy ở đây mà tôi chỉ sử dụng bên trong ứng dụng Django của mình .

Tôi đã có lớp SeatInterfacelà giao diện cho Seatlớp mô hình. Vì vậy, bên trong seat_interfacemô-đun của tôi, tôi có một nhập khẩu như vậy:

from ..models import Seat

class SeatInterface(object):
    (...)

Tôi muốn tạo các bài kiểm tra riêng biệt cho SeatInterfacelớp với Seatlớp bị chế giễu là FakeSeat. Vấn đề là - làm thế nào để chạy thử nghiệm ngoại tuyến, nơi ứng dụng Django không hoạt động. Tôi đã có lỗi dưới đây:

Cấu hình không chính xác: Cài đặt được yêu cầu BASE_DIR, nhưng cài đặt không được định cấu hình. Bạn phải xác định biến môi trường DJANGO_SETTINGS_MODULE hoặc gọi cài đặt. Thông minh () trước khi truy cập cài đặt.

Chạy thử nghiệm 1 trong 0,078s

FAILED (lỗi = 1)

Giải pháp là:

import unittest
from mock import MagicMock, patch

class FakeSeat(object):
    pass

class TestSeatInterface(unittest.TestCase):

    def setUp(self):
        models_mock = MagicMock()
        models_mock.Seat.return_value = FakeSeat
        modules = {'app.app.models': models_mock}
        patch.dict('sys.modules', modules).start()

    def test1(self):
        from app.app.models_interface.seat_interface import SeatInterface

Và sau đó kiểm tra một cách kỳ diệu chạy OK :)

.
Chạy thử 1 trong 0,002s

đồng ý


3

Nếu bạn thực hiện, import ModuleBbạn thực sự gọi phương thức dựng sẵn __import__là:

ModuleB = __import__('ModuleB', globals(), locals(), [], -1)

Bạn có thể ghi đè phương thức này bằng cách nhập __builtin__mô-đun và tạo một trình bao bọc xung quanh __builtin__.__import__phương thức. Hoặc bạn có thể chơi với NullImporterhook từ impmodule. Bắt ngoại lệ và Mock mô-đun / lớp của bạn trong except-block.

Con trỏ tới các tài liệu liên quan:

docs.python.org: __import__

Truy cập nội bộ nhập khẩu với Mô-đun imp

Tôi hi vọng cái này giúp được. Được RẤT adviced mà bạn bước vào chu vi phức tạp hơn về lập trình python và rằng a) sự hiểu biết vững chắc những gì bạn thực sự muốn đạt được và b) sự hiểu biết thấu đáo về ý nghĩa rất quan trọng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.