Вопрос

У меня есть кусок кода, который я не могу выяснить, как пройти модульный тест! Модуль вытаскивает контент из внешних каналов 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...

Моя первая мысль состояла в том, чтобы зарисовать ответ и загрузить его для тестирования, но, очевидно, объект ответа Урллиба не является неэнергетизацией (он поднимает исключение).

Просто сохранение 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 очень дружелюбен к Dacktyping, поэтому вам просто нужно обеспечить некоторое подобие свойств объекта ответа, чтобы издеваться над ним.

Например:

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 с вашими данными ответа.

Создайте отдельный класс или модуль, отвечающий за связь с вашими внешними каналами.

Сделать этот класс способным быть тест двойной. Анкет Вы используете Python, так что вы довольно золотые там; Если бы вы использовали C#, я бы предложил либо в интерфейсе, либо в виртуальных методах.

В вашем модульном тесте вставьте тестовый двойник внешнего класса подачи. Проверьте, что ваш код правильно использует класс, предполагая, что класс выполняет работу по правильному общению с вашими внешними ресурсами. Проведите поддельные данные с двойным возвратом, а не живые данные; Проверьте различные комбинации данных, и, конечно, возможные исключения, которые URLLIB2 может бросить.

А и ... вот и все.

Вы не можете эффективно автоматизировать модульные тесты, которые полагаются на внешние источники, так что вам лучше всего не делая этого. Анкет Запустите случайный тест интеграции в своем модуле связи, но не включайте эти тесты как часть ваших автоматических тестов.

Редактировать:

Просто записка о разнице между моим ответом и ответом @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.

Почему бы не просто Вытекать на сайте Это возвращает ответ, который вы ожидаете? Затем запустите сервер в потоке в настройке и убейте его в разрыв. В итоге я сделал это для тестирования кода, который отправил бы электронное письмо, издеваясь над 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-Rooy

Должен работать с Python 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