วิธีการจำลองการนำเข้า


144

โมดูลAรวมimport Bที่ด้านบน อย่างไรก็ตามภายใต้เงื่อนไขการทดสอบผมอยากจะเยาะเย้ย BในA(จำลองA.B) Bและสมบูรณ์ละเว้นจากการนำเข้า

ในความเป็นจริงBไม่ได้ติดตั้งในสภาพแวดล้อมการทดสอบตามวัตถุประสงค์

Aเป็นหน่วยที่อยู่ภายใต้การทดสอบ ฉันต้องนำเข้าAพร้อมฟังก์ชั่นทั้งหมดของมัน Bเป็นโมดูลที่ฉันต้องเยาะเย้ย แต่ฉันจะเยาะเย้ยBภายในAและหยุดAจากการนำเข้าจริงBถ้าสิ่งแรกที่Aทำก็คือนำเข้าB?

(เหตุผลที่ยังไม่ได้ติดตั้ง B คือฉันใช้ pypy สำหรับการทดสอบอย่างรวดเร็วและน่าเสียดายที่ B ยังไม่สามารถใช้งานร่วมกับ pypy ได้)

สิ่งนี้จะเกิดขึ้นได้อย่างไร?

คำตอบ:


134

คุณสามารถกำหนดให้sys.modules['B']ก่อนนำเข้าAสิ่งที่คุณต้องการ:

test.py :

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

print(A.B.__name__)

A.py :

import B

หมายเหตุ B.py ไม่ได้อยู่ แต่เมื่อทำงานtest.pyไม่มีข้อผิดพลาดจะถูกส่งกลับและภาพพิมพ์print(A.B.__name__) mock_Bคุณยังมีการสร้างmock_B.pyที่คุณเยาะเย้ยB's ฟังก์ชั่นที่เกิดขึ้นจริง / ตัวแปร / ฯลฯ หรือคุณสามารถกำหนดMock()โดยตรง:

test.py :

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

3
เก็บไว้ในใจว่าMockจะไม่แก้ไขแอตทริบิวต์มายากลบางคน ( __%s__) __name__เช่น
reclosedev

7
@reclosedev - มีการเยาะเย้ย Magicสำหรับนั้น
Jonathan

2
คุณเลิกทำสิ่งนี้เพื่อให้การนำเข้า B เป็น ImportError อีกครั้งได้อย่างไร ฉันพยายามsys.modules['B'] = Noneแต่ดูเหมือนจะไม่ทำงาน
audiodude

2
คุณจะรีเซ็ตการนำเข้าที่เยาะเย้ยนี้ในตอนท้ายของการทดสอบอย่างไรเพื่อให้ไฟล์ทดสอบหน่วยอื่นไม่ได้รับผลกระทบจากวัตถุที่เยาะเย้ย
Riya John

1
เพื่อความชัดเจนคุณควรแก้ไขคำตอบเพื่อนำเข้าจริงmockแล้วโทรmock.Mock()
nmz787

28

builtin __import__สามารถเยาะเย้ยด้วยห้องสมุด 'จำลอง' สำหรับการควบคุมเพิ่มเติม:

# 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

พูดว่าAดูเหมือน:

import B

def a():
    return B.func()

A.a()ผลตอบแทนb_mock.func()ซึ่งสามารถล้อเลียนด้วย

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

หมายเหตุสำหรับงูหลามที่ 3: ตามที่ระบุไว้ในการเปลี่ยนแปลงสำหรับ 3.0 , __builtin__การตั้งชื่อในขณะนี้builtins:

เปลี่ยนชื่อโมดูล__builtin__เป็นbuiltins(ลบเครื่องหมายขีดล่าง, เพิ่ม 's')

รหัสในคำตอบนี้ใช้ได้ดีถ้าคุณแทนที่__builtin__ด้วยbuiltinsสำหรับ Python 3


1
มีใครยืนยันงานนี้บ้าง ฉันเห็นimport_mockได้รับการเรียกimport Aแต่ไม่ได้นำเข้าอะไร
Jonathon Reinhart

3
ด้วย Python 3.4.3 ฉันได้รับImportError: No module named '__builtin__'
Lucas Cimon

คุณต้องนำเข้า__builtin__
Aidenhjj

1
@LucasCimon, แทนที่__builtin__ด้วยbuiltinsสำหรับ python3 ( docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__ )
Luke Marlin

17

วิธีการจำลองการนำเข้า (mock AB)

โมดูล A รวมถึงการนำเข้า B ที่ด้านบน

ง่ายเพียงจำลองไลบรารีใน sys.modules ก่อนที่จะได้รับการนำเข้า:

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

จากนั้นตราบAใดที่ไม่ต้องพึ่งพาข้อมูลชนิดใดประเภทหนึ่งที่ถูกส่งคืนจากวัตถุของ B:

import A

ควรจะทำงาน

คุณยังสามารถเยาะเย้ยimport A.B:

วิธีนี้ใช้ได้แม้ว่าคุณจะมี submodules แต่คุณจะต้องจำลองแต่ละโมดูล สมมติว่าคุณมีสิ่งนี้:

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

หากต้องการเยาะเย้ยเพียงทำด้านล่างก่อนที่จะนำเข้าโมดูลที่มีด้านบน:

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

(ประสบการณ์ของฉัน: ฉันมีการพึ่งพาซึ่งทำงานบนแพลตฟอร์มเดียว Windows แต่ไม่ได้ทำงานบน Linux ที่เราทำการทดสอบรายวันดังนั้นฉันต้องจำลองการพึ่งพาสำหรับการทดสอบของเราโชคดีที่มันเป็นกล่องดำดังนั้น ฉันไม่จำเป็นต้องตั้งค่าการโต้ตอบมากมาย)

ผลข้างเคียงที่เยาะเย้ย

ภาคผนวก: จริง ๆ แล้วฉันต้องการจำลองผลข้างเคียงที่ใช้เวลาพอสมควร ดังนั้นฉันต้องการวิธีการของวัตถุที่จะนอนหลับเป็นครั้งที่สอง ที่จะทำงานเช่นนี้:

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)

จากนั้นรหัสใช้เวลาในการรันเช่นเดียวกับวิธีการจริง


7

ฉันรู้ว่าฉันมาช้าไปงานปาร์ตี้ที่นี่ แต่นี่เป็นวิธีที่ค่อนข้างบ้าที่จะทำสิ่งนี้โดยอัตโนมัติด้วยmockห้องสมุด:

(นี่คือตัวอย่างการใช้งาน)

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

เหตุผลที่ซับซ้อนอย่างน่าขันก็คือเมื่อมีการนำเข้าเกิดขึ้นโดยทั่วไปแล้วงูเหลือมทำสิ่งนี้ (ใช้เป็นตัวอย่างfrom herp.derp import foo)

  1. ไม่sys.modules['herp']อยู่? อื่นนำเข้ามัน ถ้ายังไม่ได้ImportError
  2. ไม่sys.modules['herp.derp']อยู่? อื่นนำเข้ามัน ถ้ายังไม่ได้ImportError
  3. รับแอตทริบิวต์ของfoo sys.modules['herp.derp']อื่นImportError
  4. foo = sys.modules['herp.derp'].foo

มีข้อเสียบางประการสำหรับการแฮ็กข้อมูลนี้ด้วยกัน: หากมีสิ่งอื่นที่ต้องอาศัยสิ่งอื่นในเส้นทางโมดูลสกรูชนิดนี้จะจบลง นอกจากนี้ยังใช้งานได้เฉพาะกับสิ่งที่กำลังนำเข้าอินไลน์เช่น

def foo():
    import herp.derp

หรือ

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

7

คำตอบของ Aaron Hall เหมาะสำหรับฉัน แค่อยากพูดถึงสิ่งสำคัญสิ่งหนึ่ง

ถ้าอยู่ในA.pyคุณ

from B.C.D import E

จากนั้นในtest.pyคุณจะต้องเยาะเย้ยทุกโมดูลไปตามเส้นทางมิฉะนั้นคุณจะได้รับImportError

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

4

ฉันพบวิธีที่ดีในการจำลองการนำเข้าใน Python เป็นโซลูชันZaadi ของ Eric ที่นี่ซึ่งฉันเพิ่งใช้ในแอปพลิเคชันDjangoของฉัน

ฉันมีคลาสSeatInterfaceซึ่งเป็นส่วนต่อประสานกับSeatคลาสของโมเดล ดังนั้นภายในseat_interfaceโมดูลของฉันฉันมีการนำเข้าเช่น:

from ..models import Seat

class SeatInterface(object):
    (...)

ฉันอยากจะสร้างการทดสอบแยกสำหรับSeatInterfaceชั้นเรียนกับเยาะเย้ยระดับเป็นSeat FakeSeatปัญหาคือ - วิธีทดสอบการทำงานออฟไลน์โดยที่แอปพลิเคชัน Django หยุดทำงาน ฉันมีข้อผิดพลาดด้านล่าง:

ImproflyConfigured: การตั้งค่าที่ร้องขอ BASE_DIR แต่การตั้งค่าไม่ได้กำหนดค่า คุณต้องกำหนดตัวแปรสภาพแวดล้อม DJANGO_SETTINGS_MODULE หรือการตั้งค่าการโทรการกำหนดค่า () ก่อนที่จะเข้าถึงการตั้งค่า

ทดสอบ 1 ครั้งใน 0.078 วินาที

ล้มเหลว (ข้อผิดพลาด = 1)

ทางออกคือ:

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

แล้วทดสอบอย่างน่าอัศจรรย์ทำงานตกลง :)

.
ทดสอบ 1 ครั้งใน 0.002 วินาที

ตกลง


3

หากคุณทำสิ่งที่import ModuleBคุณกำลังเรียกวิธี builtin __import__เป็น:

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

คุณสามารถเขียนทับวิธีนี้ได้โดยการนำเข้า__builtin__โมดูลและทำกระดาษคลุมรอบ__builtin__.__import__วิธีนั้น หรือคุณสามารถเล่นกับNullImporterตะขอจากimpโมดูล การจับข้อยกเว้นและจำลองโมดูล / คลาสของคุณในexcept-block

ชี้ไปที่เอกสารที่เกี่ยวข้อง:

docs.python.org: __import__

การเข้าถึงการนำเข้า internals ด้วยโมดูล imp

ฉันหวังว่านี่จะช่วยได้. ได้รับคำแนะนำอย่างสูงว่าคุณก้าวเข้าสู่ขอบเขตการเขียนโปรแกรมหลามที่มากขึ้นและ a) การเข้าใจอย่างถ่องแท้ในสิ่งที่คุณต้องการบรรลุและข) การเข้าใจอย่างลึกซึ้งเกี่ยวกับผลกระทบนั้นเป็นสิ่งสำคัญ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.