Cara mengejek impor

144

Modul Atermasuk import Bdi atasnya. Namun di bawah kondisi pengujian saya ingin mengejek B di A(mock A.B) dan benar-benar menahan diri dari impor B.

Bahkan, Btidak dipasang di lingkungan pengujian dengan sengaja.

Aadalah unit yang sedang diuji. Saya harus mengimpor Adengan semua fungsinya. Badalah modul yang perlu saya tiru. Tetapi bagaimana saya bisa mengejek Bdalam Adan berhenti Amengimpor yang asli B, jika hal pertama yang Adilakukan adalah mengimpor B?

(Alasan B tidak diinstal adalah bahwa saya menggunakan pypy untuk pengujian cepat dan sayangnya B belum kompatibel dengan pypy.)

Bagaimana ini bisa dilakukan?

Jonathan
sumber

Jawaban:

134

Anda dapat menetapkan sys.modules['B']sebelum mengimpor Auntuk mendapatkan apa yang Anda inginkan:

test.py :

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

print(A.B.__name__)

A.py :

import B

Catatan B.py tidak ada, tetapi saat berjalan test.pytidak ada kesalahan yang dikembalikan dan print(A.B.__name__)dicetak mock_B. Anda masih harus membuat di mock_B.pymana Anda mengejek Bfungsi aktual / variabel / dll. Atau Anda bisa langsung menetapkan Mock():

test.py :

import sys
sys.modules['B'] = Mock()
import A
Rob Wouters
sumber
3
perlu diingat bahwa Mocktidak akan menambal beberapa atribut ajaib ( __%s__) seperti __name__.
reclosedev
7
@reclosedev - ada Magic Mock untuk itu
Jonathan
2
Bagaimana Anda membatalkan ini sehingga impor B adalah ImportError lagi? Saya mencoba sys.modules['B'] = Nonetetapi tampaknya tidak berhasil.
audiodude
2
Bagaimana Anda mengatur ulang impor yang diolok-olok ini di akhir tes, sehingga file tes unit lain tidak terpengaruh oleh objek yang diolok-olok?
Riya John
1
untuk kejelasan, Anda harus mengedit jawaban untuk benar-benar mengimpor mockdan kemudian memanggilmock.Mock()
nmz787
28

Builtin __import__dapat diejek dengan perpustakaan 'mock' untuk kontrol lebih lanjut:

# 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

Say Asepertinya:

import B

def a():
    return B.func()

A.a()pengembalian b_mock.func()yang bisa diejek juga.

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

Catatan untuk Python 3: Sebagaimana dinyatakan dalam changelog untuk 3.0 , __builtin__sekarang dinamai builtins:

Mengganti nama modul __builtin__menjadi builtins(menghapus garis bawah, menambahkan 's').

Kode dalam jawaban ini berfungsi dengan baik jika Anda mengganti __builtin__dengan builtinsuntuk Python 3.

siebz0r
sumber
1
Adakah yang mengkonfirmasi ini berfungsi? Saya melihat import_mockdipanggil untuk import A, tetapi tidak untuk apa pun yang diimpor.
Jonathon Reinhart
3
Dengan Python 3.4.3 saya dapatkanImportError: No module named '__builtin__'
Lucas Cimon
Anda harus mengimpor__builtin__
Aidenhjj
1
@LucasCimon, ganti __builtin__dengan builtinsuntuk python3 ( docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__ )
Luke Marlin
17

Bagaimana cara mengejek impor, (mengejek AB)?

Modul A termasuk impor B di atasnya.

Mudah, cukup mengejek perpustakaan di sys.modules sebelum diimpor:

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

dan kemudian, selama Atidak bergantung pada tipe data tertentu yang dikembalikan dari objek B:

import A

seharusnya hanya bekerja.

Anda juga bisa mengejek import A.B:

Ini berfungsi bahkan jika Anda memiliki submodula, tetapi Anda ingin mengejek setiap modul. Katakan Anda memiliki ini:

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

Untuk mengejek, cukup lakukan di bawah ini sebelum modul yang berisi di atas diimpor:

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

(Pengalaman saya: Saya memiliki ketergantungan yang bekerja pada satu platform, Windows, tetapi tidak bekerja di Linux, di mana kami menjalankan tes harian kami. Jadi saya perlu mengejek ketergantungan untuk tes kami. Untungnya itu adalah kotak hitam, jadi Saya tidak perlu mengatur banyak interaksi.)

Efek Samping Mengejutkan

Tambahan: Sebenarnya, saya perlu mensimulasikan efek samping yang memerlukan waktu. Jadi saya perlu metode objek untuk tidur sebentar. Itu akan bekerja seperti ini:

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)

Dan kemudian kode membutuhkan waktu untuk berjalan, seperti metode yang sebenarnya.

Aaron Hall
sumber
7

Saya menyadari bahwa saya agak terlambat ke pesta di sini, tapi ini cara yang agak gila untuk mengotomatisasi ini dengan mockperpustakaan:

(inilah contoh penggunaan)

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

Alasan mengapa hal ini sangat rumit adalah ketika suatu impor terjadi pada dasarnya python melakukan ini (ambil contoh from herp.derp import foo)

  1. Apakah sys.modules['herp']ada Lain impor saja. Jika masih belumImportError
  2. Apakah sys.modules['herp.derp']ada Lain impor saja. Jika masih belumImportError
  3. Dapatkan atribut foodari sys.modules['herp.derp']. LainImportError
  4. foo = sys.modules['herp.derp'].foo

Ada beberapa kerugian untuk solusi yang diretas bersama ini: Jika ada hal lain yang bergantung pada hal-hal lain di jalur modul, sekrupkan ini. Ini juga hanya berfungsi untuk barang yang sedang diimpor inline seperti

def foo():
    import herp.derp

atau

def foo():
    __import__('herp.derp')
Anthony Sottile
sumber
7

Jawaban Aaron Hall bekerja untuk saya. Hanya ingin menyebutkan satu hal penting,

jika di dalam A.pykamu lakukan

from B.C.D import E

maka di dalam test.pykamu harus mengejek setiap modul di sepanjang jalan, jika tidak kamu dapatkanImportError

sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()
Qingyi Wu
sumber
4

Saya menemukan cara yang bagus untuk mengejek impor dengan Python. Ini adalah solusi Erica Zaadi yang ditemukan di sini yang saya gunakan di dalam aplikasi Django saya .

Saya punya kelas SeatInterfaceyang merupakan antarmuka ke Seatkelas model. Jadi di dalam seat_interfacemodul saya, saya memiliki impor seperti itu:

from ..models import Seat

class SeatInterface(object):
    (...)

Saya ingin membuat tes terisolasi untuk SeatInterfacekelas dengan Seatkelas mengejek FakeSeat. Masalahnya adalah - bagaimana Anda menjalankan tes offline, di mana aplikasi Django sedang down. Saya memiliki kesalahan di bawah ini:

Konfigurasi Tidak Benar: Pengaturan yang diminta BASE_DIR, tetapi pengaturan tidak dikonfigurasi. Anda harus menentukan variabel lingkungan DJANGO_SETTINGS_MODULE atau memanggil pengaturan.configure () sebelum mengakses pengaturan.

Berlari 1 tes dalam 0,078-an

GAGAL (kesalahan = 1)

Solusinya adalah:

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

Dan kemudian uji ajaib berjalan OK :)

.
Uji 1 dalam 0,002s

baik

Hunter_71
sumber
3

Jika Anda melakukannya, import ModuleBAnda benar-benar memanggil metode builtin __import__sebagai:

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

Anda bisa menimpa metode ini dengan mengimpor __builtin__modul dan membuat pembungkus di sekitar __builtin__.__import__metode ini. Atau Anda bisa bermain dengan NullImporterpengait dari impmodul. Menangkap pengecualian dan mengejek modul / kelas Anda di except-block.

Menunjuk ke dokumen yang relevan:

docs.python.org: __import__

Mengakses internal impor dengan Modul imp

Saya harap ini membantu. Jadilah SANGAT disarankan bahwa Anda melangkah ke batas yang lebih misterius dari pemrograman python dan bahwa a) pemahaman yang kuat tentang apa yang benar-benar ingin Anda capai dan b) pemahaman menyeluruh tentang implikasi itu penting.

Don Question
sumber