我有一段代码,我无法弄清楚如何进行单元测试!该模块是直接从与外部的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是不理想的,因为我的代码使用的头信息了。它设计成作用的响应对象。

当然,依赖于用于在单元测试数据的外部源是可怕主意。

那么,如何编写一个单元测试呢?

有帮助吗?

解决方案

的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对鸭子类型非常友好,所以你只需要提供响应对象的属性的一些假象来嘲笑它。

例如:

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可能抛出的各种组合。

A和......就是这样。

您不能有效地自动单元测试依赖于外部资源,所以你最好的不这样做的。运行您的通讯模块上偶尔集成测试,但不包括那些测试作为自动化测试的一部分。

修改

这是我的答案之间和@ Crast的答案的区别只是注意。两者本质上是正确的,但它们涉及不同的方法。在Crast的方法,可以使用在库本身的测试双。在我的方法,你抽象的使用图书馆的走成一个单独的模块和测试两倍模块。

哪种方法使用完全是主观

有没有“正确”的答案在那里。我喜欢我的方法,因为它可以让我建立更多的模块化的,灵活的代码,这是我的价值。但说到在额外的代码来写,这并不会在许多情况下,灵活进行估值方面的代价。

您可以使用 pymox 嘲笑在urllib2的任何东西,一切的行为(或任何其他)封装。这是2010年,你不应该写自己的mock类。

我觉得最简单的办法就是实际创建单元测试一个简单的Web服务器。当您启动测试,创建一个新的线程,监听一些任意的端口上,当客户端连接只返回一个已知的套头和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。

为什么不直接嘲笑返回你所期望的响应网站?然后启动服务器中设置一个线程,并在拆卸杀死它。最后我做这个测试代码会被嘲笑的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"

试图改善@约翰-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