복붙노트

[PYTHON] urllib2를 사용하는 모듈을 단위 테스트하려면 어떻게합니까?

PYTHON

urllib2를 사용하는 모듈을 단위 테스트하려면 어떻게합니까?

단위 테스트하는 방법을 알 수없는 코드 조각이 있습니다! 이 모듈은 urllib2로 외부 XML 피드 (twitter, flickr, youtube 등)의 컨텐츠를 가져옵니다. 여기에 몇 가지 의사 코드가 있습니다.

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을 응답 본문에서 저장하는 것은 이상적이지 않습니다. 응답 객체에 대해 작동하도록 설계되었습니다.

물론 단위 테스트에서 외부 소스를 사용하는 것은 끔찍한 생각입니다.

그렇다면 이것을위한 단위 테스트를 작성하려면 어떻게해야합니까?

해결법

  1. ==============================

    1.urllib2에는 urlopen ()의 동작을 조롱하는 데 사용해야하는 build_opener () 및 install_opener ()라는 함수가 있습니다.

    urllib2에는 urlopen ()의 동작을 조롱하는 데 사용해야하는 build_opener () 및 install_opener ()라는 함수가 있습니다.

    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
    
  2. ==============================

    2.urllib2의 버전처럼 동작하는 데 필요한 최소한의 인터페이스를 제공하는 mock urlopen (및 아마도 Request)을 작성할 수 있다면 가장 좋을 것입니다. 그런 다음이 모의 urlopen을 어떻게 든 받아 들일 수있는 함수 / 메소드가 필요하며 그렇지 않으면 urllib2.urlopen을 사용하십시오.

    urllib2의 버전처럼 동작하는 데 필요한 최소한의 인터페이스를 제공하는 mock urlopen (및 아마도 Request)을 작성할 수 있다면 가장 좋을 것입니다. 그런 다음이 모의 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>')
    

    예를 들어, 정상적인 "헤더"는 대소 문자를 구분하지 않는 헤더 이름과 같은 재미있는 것을 구현하는 HTTP 메시지라고 생각하기 때문에, 이들 중 일부는 모의하기가 어렵습니다. 그러나 응답 데이터로 HTTPMessage를 간단하게 작성할 수 있습니다.

  3. ==============================

    3.외부 피드와 통신 할 책임이있는 별도의 클래스 또는 모듈을 작성하십시오.

    외부 피드와 통신 할 책임이있는 별도의 클래스 또는 모듈을 작성하십시오.

    이 클래스를 테스트 이중으로 만들 수 있습니다. 당신은 파이썬을 사용하고 있습니다. 그래서 당신은 꽤 황금색입니다. C #을 사용한다면 인터페이스 나 가상 메소드 중 하나를 제안 할 것입니다.

    단위 테스트에서 외부 피드 클래스의 테스트 이중을 삽입하십시오. 클래스가 외부 리소스와 올바르게 통신하는 작업을 수행한다고 가정하고 코드가 클래스를 올바르게 사용하는지 테스트하십시오. 테스트가 라이브 데이터가 아닌 가짜 데이터를 반환하도록하십시오. 데이터의 다양한 조합을 테스트하고 물론 urllib2가 던질 수있는 예외의 가능성을 테스트하십시오.

    그리고 그게 다야.

    외부 소스에 의존하는 단위 테스트를 효과적으로 자동화 할 수 없기 때문에 최선의 방법은 아닙니다. 통신 모듈에 대한 간헐적 인 통합 테스트를 실행하지만 자동화 된 테스트의 일부로 이러한 테스트를 포함시키지 마십시오.

    편집하다:

    내 대답과 @ Crast의 대답의 차이에 대한 메모. 두 가지 모두 본질적으로 옳지 만 서로 다른 접근 방식을 필요로합니다. Crast의 접근법에서는 라이브러리 자체에 대한 테스트 double을 사용합니다. 필자의 접근 방식에서는 라이브러리의 사용을 별도의 모듈로 추상화하고 해당 모듈을 두 배 테스트합니다.

    어떤 접근 방식을 사용하는 것은 전적으로 주관적입니다. 거기에 "올바른"대답이 없습니다. 필자가 선호하는 더 많은 모듈화되고 유연한 코드를 구축 할 수 있기 때문에 필자의 접근 방식을 선호한다. 그러나 많은 민첩한 상황에서 가치가 없을 수도있는 추가 코드 작성 비용 측면에서 비용이 발생합니다.

  4. ==============================

    4.pymox를 사용하여 urllib2 (또는 다른 패키지)의 모든 동작을 조롱 할 수 있습니다. 2010 년, 자신 만의 모의 수업을 작성하면 안됩니다.

    pymox를 사용하여 urllib2 (또는 다른 패키지)의 모든 동작을 조롱 할 수 있습니다. 2010 년, 자신 만의 모의 수업을 작성하면 안됩니다.

  5. ==============================

    5.가장 쉬운 방법은 실제로 단위 테스트에서 간단한 웹 서버를 만드는 것입니다. 테스트를 시작할 때 임의의 포트에서 수신 대기하는 새 스레드를 만들고 클라이언트가 연결할 때 알려진 헤더와 XML 집합을 반환 한 다음 종료합니다.

    가장 쉬운 방법은 실제로 단위 테스트에서 간단한 웹 서버를 만드는 것입니다. 테스트를 시작할 때 임의의 포트에서 수신 대기하는 새 스레드를 만들고 클라이언트가 연결할 때 알려진 헤더와 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 ()를 호출 한 다음 http : // localhost : 12345 / anythingyouwant와 같은 URL을 요청하는 코드를 호출하십시오.

  6. ==============================

    6.왜 당신이 기대하는 응답을 반환하는 웹 사이트를 조롱하지 않습니까? 다음 설치에서 스레드에서 서버를 시작하고 teardown에서 그것을 죽일. 나는 smtp 서버를 조롱하여 이메일을 보내는 테스트 코드에 대해 이렇게했다. 확실히 뭔가 더 사소한 HTTP를 할 수있는 ...

    왜 당신이 기대하는 응답을 반환하는 웹 사이트를 조롱하지 않습니까? 다음 설치에서 스레드에서 서버를 시작하고 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"
    
  7. ==============================

    7.@ john-la-rooy 답을 조금 개선하려고 시도하면서 단위 테스트를위한 간단한 조롱을 허용하는 작은 클래스를 만들었습니다.

    @ john-la-rooy 답을 조금 개선하려고 시도하면서 단위 테스트를위한 간단한 조롱을 허용하는 작은 클래스를 만들었습니다.

    파이썬 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)
    
  8. from https://stackoverflow.com/questions/2276689/how-do-i-unit-test-a-module-that-relies-on-urllib2 by cc-by-sa and MIT license