如何对依赖于urllib2的模块进行单元测试?

Gab*_*ley 26 python unit-testing urllib 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...
Run Code Online (Sandbox Code Playgroud)

我的第一个想法是挑选响应并加载它以进行测试,但显然urllib的响应对象是不可序列化的(它引发了异常).

仅仅从响应主体保存XML并不理想,因为我的代码也使用了头信息.它旨在作用于响应对象.

当然,在单元测试中依赖外部数据来源是一个可怕的想法.

那么我该如何为此编写单元测试呢?

Joh*_*ooy 26

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
Run Code Online (Sandbox Code Playgroud)

  • 这很酷,我实际上并不知道urllib2让你安装备用开启器.我看到的唯一问题是它意味着你已经改变了全局共享状态,这意味着对`urllib2.urlopen`的任何后续调用都将使用你的处理程序,除非你重新注册旧的(如果你正在运行个人就没问题)测试,但是当各种测试可能影响后续测试的结果时,可能会导致测试套件出现问题.) (4认同)
  • @Crast,其想法是改变全局行为,因为对urlopen的调用可能在某个模块的深处.将您不感兴趣的请求传递给HTTPHandler会很简单.在大多数情况下,我会在整个测试套件中使用相同的模拟开启器,然后重新安装原件. (4认同)

Cra*_*ast 9

最好是你可以编写一个mock 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>')
Run Code Online (Sandbox Code Playgroud)

当然,其中一些很难被模拟,因为例如我认为正常的"标题"是一个HTTPMessage,它实现了像case-insensitive标题名称这样的有趣的东西.但是,您可以使用响应数据简单地构造HTTPMessage.


Ran*_*pho 6

构建一个单独的类或模块,负责与外部源进行通信.

使这个类能够成为测试的两倍.你正在使用python,所以你在那里很漂亮; 如果您使用的是C#,我建议使用接口或虚拟方法.

在单元测试中,插入外部Feed类的测试双.测试您的代码是否正确使用该类,假设该类正确地执行与外部资源通信的工作.让你的测试双重返回假数据而不是实时数据; 测试数据的各种组合,当然还有urllib2可能抛出的异常.

Aand ......就是这样.

您无法有效地自动化依赖外部源的单元测试,因此您最好不要这样做.在您的通信模块上运行偶尔的集成测试,但不要将这些测试作为自动测试的一部分.

编辑:

请注意我的回答和@Crast的回答之间的区别.两者基本上都是正确的,但它们涉及不同的方法.在Crast的方法中,您在库本身上使用了测试双精度.在我的方法中,您将库的使用抽象为一个单独的模块并测试该模块的两倍.

你使用哪种方法完全是主观的; 那里没有"正确"的答案.我更喜欢我的方法,因为它允许我构建更多模块化,灵活的代码,这是我重视的.但是在编写额外代码方面需要付出代价,这在某些敏捷情况下可能无法得到重视.

  • 回覆.你的编辑:对于它的价值,如果我从头开始编写代码,我实际上也会使用你的方法(某种类型的url-getter类).我更喜欢在返回时只使用保证属性的子集编写最小接口,因此它们更容易进行测试双倍.此外,它使依赖注入更加明确. (2认同)
  • 想多投几次这样的话.这种方法可以比在标准库中模拟某些东西更容易模拟测试吨! (2认同)

ant*_*ony 5

您可以使用pymox来模拟urllib2(或任何其他)包中的任何内容和所有内容的行为.这是2010年,你不应该写自己的模拟课程.