سؤال

لدي قطعة رمز لا يمكنني معرفة كيفية اختبار الوحدة! تقوم الوحدة بسحب المحتوى من خلاصات XML الخارجية (Twitter ، Flickr ، YouTube ، إلخ) مع urllib2. إليكم بعض الرمز الزائف:

params = (url, urlencode(data),) if data else (url,)
req = Request(*params)
response = urlopen(req)
#check headers, content-length, etc...
#parse the response XML with lxml...

كانت فكرتي الأولى هي مخلل الاستجابة وتحميلها للاختبار ، ولكن يبدو أن كائن استجابة Urllib أمر غير محترم (يثير استثناء).

مجرد حفظ XML من هيئة الاستجابة ليس مثاليًا ، لأن الكود الخاص بي يستخدم معلومات الرأس أيضًا. إنه مصمم للعمل على كائن استجابة.

وبالطبع ، فإن الاعتماد على مصدر خارجي للبيانات في اختبار الوحدة هو رهيب فكرة.

فكيف أكتب اختبار الوحدة لهذا؟

هل كانت مفيدة؟

المحلول

urllib2 لديه وظائف تسمى build_opener() و install_opener() الذي يجب أن تستخدمه للسخرية من سلوك urlopen()

import urllib2
from StringIO import StringIO

def mock_response(req):
    if req.get_full_url() == "http://example.com":
        resp = urllib2.addinfourl(StringIO("mock file"), "mock message", req.get_full_url())
        resp.code = 200
        resp.msg = "OK"
        return resp

class MyHTTPHandler(urllib2.HTTPHandler):
    def http_open(self, req):
        print "mock opener"
        return mock_response(req)

my_opener = urllib2.build_opener(MyHTTPHandler)
urllib2.install_opener(my_opener)

response=urllib2.urlopen("http://example.com")
print response.read()
print response.code
print response.msg

نصائح أخرى

سيكون من الأفضل لو تمكنت من كتابة عنوان URLOPEN وهمية (وربما طلب) والذي يوفر الحد الأدنى للواجهة المطلوبة للتصرف مثل إصدار URLLIB2. ستحتاج بعد ذلك إلى الحصول على وظيفتك/طريقتك التي تستخدمها قادرة على قبول عنوان urlopen وهمية بطريقة أو بأخرى ، واستخدامها urllib2.urlopen خلاف ذلك.

هذا قدر لا بأس به من العمل ، ولكنه يستحق العناء. تذكر أن Python ودية للغاية بالنسبة إلى Ducktyping ، لذلك تحتاج فقط إلى توفير بعض مظاهر خصائص كائن الاستجابة للسخرية منه.

فمثلا:

class MockResponse(object):
    def __init__(self, resp_data, code=200, msg='OK'):
        self.resp_data = resp_data
        self.code = code
        self.msg = msg
        self.headers = {'content-type': 'text/xml; charset=utf-8'}

    def read(self):
        return self.resp_data

    def getcode(self):
        return self.code

    # Define other members and properties you want

def mock_urlopen(request):
    return MockResponse(r'<xml document>')

من المسلم به أن بعضها يصعب تسريعه ، لأنه على سبيل المثال أعتقد أن "الرؤوس" العادية هي httpmessage التي تنفذ أشياء ممتعة مثل أسماء رؤوس غير حساسة للحالة. ولكن ، قد تكون قادرًا على إنشاء httpmessage مع بيانات الاستجابة الخاصة بك.

قم بإنشاء فئة أو وحدة نمطية منفصلة مسؤولة عن التواصل مع الخلاصات الخارجية.

اجعل هذا الفصل قادرًا على أن يكون اختبار مزدوج. أنت تستخدم بيثون ، لذلك أنت ذهبي جدا هناك ؛ إذا كنت تستخدم C#، أقترح إما في الواجهة أو الأساليب الافتراضية.

في اختبار الوحدة ، أدخل اختبارًا مزدوجًا لفئة التغذية الخارجية. اختبر أن الكود الخاص بك يستخدم الفصل بشكل صحيح ، على افتراض أن الفصل يقوم بعمل التواصل مع مواردك الخارجية بشكل صحيح. اجعل الاختبار المزدوج بيانات مزيفة بدلاً من البيانات الحية ؛ اختبار مجموعات مختلفة من البيانات وبالطبع الاستثناءات المحتملة التي يمكن أن يرميها urllib2.

Aand ... هذا كل شيء.

لا يمكنك أتمتة اختبارات الوحدة التي تعتمد بشكل فعال على المصادر الخارجية ، لذلك من الأفضل أن تكون أفضل لا تفعل ذلك. قم بإجراء اختبار تكامل عرضي على وحدة الاتصال الخاصة بك ، ولكن لا تتضمن تلك الاختبارات كجزء من الاختبارات الآلية.

يحرر:

مجرد ملاحظة حول الفرق بين إجابتي وإجابة @Crast. كلاهما صحيح بشكل أساسي ، لكنه ينطوي على طرق مختلفة. في نهج Crast ، يمكنك استخدام اختبار مزدوج على المكتبة نفسها. في مقاربي ، تقوم بتجريد استخدام المكتبة بعيدًا في وحدة نمطية منفصلة واختبار هذه الوحدة النمطية.

ما هو النهج الذي تستخدمه هو شخصي تمامًا ؛ لا يوجد إجابة "صحيحة" هناك. أنا أفضل نهجتي لأنه يسمح لي ببناء رمز أكثر وحدات ومرنة ، شيء أقدره. لكنه يأتي بتكلفة من حيث التعليمات البرمجية الإضافية للكتابة ، وهو أمر قد لا يتم تقديره في العديد من المواقف الرشيقة.

يمكنك استخدام pymox للسخرية من سلوك أي شيء وكل شيء في حزمة urllib2 (أو أي أخرى). إنه عام 2010 ، يجب ألا تكتب فصولك الوهمية.

أعتقد أن أسهل شيء يجب فعله هو إنشاء خادم ويب بسيط في اختبار الوحدة. عند بدء الاختبار ، قم بإنشاء سلسلة رسائل جديدة تستمع إلى بعض المنفذ التعسفي وعندما يقوم العميل بإرجاع مجموعة معروفة من الرؤوس و XML ، ثم ينتهي.

يمكنني توضيح إذا كنت بحاجة إلى مزيد من المعلومات.

إليك بعض التعليمات البرمجية:

import threading, SocketServer, time

# a request handler
class SimpleRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(102400) # token receive
        senddata = file(self.server.datafile).read() # read data from unit test file
        self.request.send(senddata)
        time.sleep(0.1) # make sure it finishes receiving request before closing
        self.request.close()

def serve_data(datafile):
    server = SocketServer.TCPServer(('127.0.0.1', 12345), SimpleRequestHandler)
    server.datafile = datafile
    http_server_thread = threading.Thread(target=server.handle_request())

لتشغيل اختبار الوحدة الخاص بك ، اتصل serve_data() ثم اتصل بالشفرة التي تطلب عنوان URL الذي يبدو http://localhost:12345/anythingyouwant.

لماذا لا فقط سخرية موقع ويب هذا يعيد الاستجابة التي تتوقعها؟ ثم ابدأ الخادم في مؤشر ترابط في الإعداد وقتله في teardown. انتهى بي الأمر إلى القيام بذلك للاختبار رمز من شأنه أن يرسل بريدًا إلكترونيًا عن طريق السخرية من خادم SMTP ويعمل بشكل رائع. بالتأكيد يمكن القيام بشيء أكثر تافهة لـ HTTP ...

from smtpd import SMTPServer
from time import sleep
import asyncore
SMTP_PORT = 6544

class MockSMTPServer(SMTPServer):
    def __init__(self, localaddr, remoteaddr, cb = None):
        self.cb = cb
        SMTPServer.__init__(self, localaddr, remoteaddr)

    def process_message(self, peer, mailfrom, rcpttos, data):
        print (peer, mailfrom, rcpttos, data)
        if self.cb:
            self.cb(peer, mailfrom, rcpttos, data)
        self.close()

def start_smtp(cb, port=SMTP_PORT):

    def smtp_thread():
        _smtp = MockSMTPServer(("127.0.0.1", port), (None, 0), cb)
        asyncore.loop()
        return Thread(None, smtp_thread)


def test_stuff():
        #.......snip noise
        email_result = None

        def email_back(*args):
            email_result = args

        t = start_smtp(email_back)
        t.start()
        sleep(1)

        res.form["email"]= self.admin_email
        res = res.form.submit()
        assert res.status_int == 302,"should've redirected"


        sleep(1)
        assert email_result is not None, "didn't get an email"

في محاولة للتحسين قليلاً على إجابة @John-La-Rrooy ، قمت بعمل فصل صغير يسمح بسخرية بسيطة لاختبارات الوحدة

يجب أن تعمل مع بيثون 2 و 3

try:
    import urllib.request as urllib
except ImportError:
    import urllib2 as urllib

from io import BytesIO


class MockHTTPHandler(urllib.HTTPHandler):

    def mock_response(self, req):
        url = req.get_full_url()

        print("incomming request:", url)

        if url.endswith('.json'):
            resdata = b'[{"hello": "world"}]'
            headers = {'Content-Type': 'application/json'}

            resp = urllib.addinfourl(BytesIO(resdata), header, url, 200)
            resp.msg = "OK"

            return resp
        raise RuntimeError('Unhandled URL', url)
    http_open = mock_response


    @classmethod
    def install(cls):
        previous = urllib._opener
        urllib.install_opener(urllib.build_opener(cls))
        return previous

    @classmethod
    def remove(cls, previous=None):
        urllib.install_opener(previous)

تستخدم مثل هذا:

class TestOther(unittest.TestCase):

    def setUp(self):
        previous = MockHTTPHandler.install()
        self.addCleanup(MockHTTPHandler.remove, previous)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top