Как выполнить модульное тестирование модуля, основанного на urllib2?

В Java все находится в форме класса.

Если вы хотите использовать любой объект, тогда у вас есть две фазы:

  1. Объявить
  2. Инициализация

Пример:

  • Объявление: Object a;
  • Инициализация: a=new Object();

То же самое для концепции массива

  • Объявление: Item i[]=new Item[5];
  • Инициализация: i[0]=new Item();

Если вы не дают секцию инициализации, тогда возникает NullpointerException.

25
задан Gabriel Hurley 16 February 2010 в 22:02
поделиться

7 ответов

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
25
ответ дан John La Rooy 16 February 2010 в 22:02
поделиться

Было бы лучше, если бы вы могли написать фиктивный urlopen (и, возможно, Request), который предоставляет минимально необходимый интерфейс, чтобы вести себя как версия 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 с вашими данными ответа.

9
ответ дан Crast 16 February 2010 в 22:02
поделиться

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

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

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

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

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

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

Просто заметка о разнице между моим ответом и ответом @ Crast. И то, и другое по сути правильно, но они предполагают разные подходы. В подходе Crast вы используете тестовый дубль на самой библиотеке. В моем подходе вы абстрагируете использование библиотеки в отдельный модуль и тестируете двойной модуль.

Какой подход вы используете, является полностью субъективным; там нет "правильного" ответа. Я предпочитаю свой подход, потому что он позволяет мне создавать более модульный, гибкий код, что я ценю. Но это обходится дорого с точки зрения написания дополнительного кода, что не может быть оценено во многих гибких ситуациях.

6
ответ дан Randolpho 16 February 2010 в 22:02
поделиться

Вы можете использовать pymox , чтобы высмеивать поведение всего и вся в пакете urllib2 (или любом другом). Это 2010 год, вы не должны писать свои собственные фиктивные классы.

5
ответ дан anthony 16 February 2010 в 22:02
поделиться

Почему бы просто не издеваться над сайтом , который возвращает ожидаемый ответ? затем запустить сервер в потоке в настройке и убить его в демонтаже. Я закончил тем, что сделал это для тестирования кода, который отправлял бы электронную почту, издеваясь над 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"
0
ответ дан Tom Willis 16 February 2010 в 22:02
поделиться

Пытаясь немного улучшить ответ @ 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)
0
ответ дан Romuald Brunet 16 February 2010 в 22:02
поделиться

Я думаю, что проще всего создать простой веб-сервер в модульном тесте. Когда вы запускаете тест, создайте новый поток, который прослушивает какой-то произвольный порт, и когда клиент подключается, просто возвращает известный набор заголовков и 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 / somethingyouwant .

1
ответ дан 28 November 2019 в 07:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: