python httpretty

张开发
2026/4/17 6:00:27 15 分钟阅读

分享文章

python httpretty
# 聊聊 Python 里的 httpretty一个让 HTTP 请求“听话”的工具在写代码的时候我们经常会遇到需要和外部服务打交道的情况比如调用某个 API 接口获取数据。这时候测试就成了一个让人头疼的问题——你总不能让测试代码真的去访问生产环境的服务器吧或者你正在开发的 API 还没上线但前端同事已经等着要联调了。这时候一个能“模拟” HTTP 响应的工具就显得特别有用。httpretty 就是这样一个工具它能让 HTTP 请求在测试环境中变得“听话”你想让它返回什么它就返回什么。httpretty 是什么简单来说httpretty 是一个 Python 库专门用来拦截和模拟 HTTP 请求。它不像那些真正的 HTTP 服务器那样需要启动一个端口监听请求而是在代码层面直接“截胡”发出去的请求然后返回你预先设定好的响应。这种机制有点像在邮局里安插了一个内部人员所有寄往某个地址的信件都被他悄悄截下来然后按照你的指示回一封伪造的回信而寄信的人完全察觉不到中间被动了手脚。它的工作原理是通过 Python 标准库里的httplib或者http.client取决于 Python 版本层面进行拦截。当你用requests、urllib或者其他常见的 HTTP 客户端库发起请求时httpretty 会在请求真正发出之前把它拦住然后根据你注册的规则返回一个模拟的响应。整个过程对业务代码是透明的你不需要修改任何发送请求的代码。httpretty 能做什么这个工具的主要用途是在测试中模拟外部服务的响应。想象一下你正在开发一个天气应用需要从气象局的 API 获取数据。在测试的时候你不可能每次都去请求真实的 API——那太慢了而且可能受网络影响甚至会产生费用。有了 httpretty你就可以在测试中模拟气象局 API 的响应比如模拟一个晴天、一个雨天或者一个 API 返回错误的场景。除了基本的模拟响应httpretty 还能记录请求的历史这样你就可以在测试中验证你的代码是否发出了正确的请求比如请求的 URL 对不对参数有没有传错请求头是否符合预期。这对于测试那些会对外发送请求的代码特别有用比如上传文件到云存储、发送消息到消息队列等等。另一个常见的场景是测试错误处理。真实的 API 可能会返回各种错误状态码比如 404、500、503 等等。要真实地触发这些错误往往不容易但用 httpretty 就可以轻松模拟这些错误响应从而确保你的错误处理逻辑是可靠的。怎么使用 httpretty使用 httpretty 并不复杂但需要遵循一定的模式。通常你会在测试的 setup 阶段启用 httpretty在 teardown 阶段关闭它。下面是一个简单的例子假设我们要测试一个函数这个函数会调用一个 API 来获取用户信息。首先假设我们有这样一个函数importrequestsdefget_user_info(user_id):responserequests.get(fhttps://api.example.com/users/{user_id})response.raise_for_status()returnresponse.json()在测试这个函数的时候我们不想真的去访问api.example.com就可以用 httpretty 来模拟这个请求的响应importhttprettyimportunittestclassTestGetUserInfo(unittest.TestCase):defsetUp(self):httpretty.enable()# 启用 httprettydeftearDown(self):httpretty.disable()# 关闭 httprettyhttpretty.reset()# 重置所有注册的请求deftest_get_user_info_success(self):# 注册一个模拟的响应httpretty.register_uri(httpretty.GET,https://api.example.com/users/123,body{id: 123, name: John Doe},content_typeapplication/json)resultget_user_info(123)self.assertEqual(result[name],John Doe)# 还可以验证请求是否真的发生了self.assertEqual(len(httpretty.latest_requests()),1)deftest_get_user_info_not_found(self):# 模拟一个 404 错误httpretty.register_uri(httpretty.GET,https://api.example.com/users/999,status404)withself.assertRaises(requests.exceptions.HTTPError):get_user_info(999)在这个例子中httpretty.register_uri方法用来告诉 httpretty当遇到匹配某个 URL 和方法的请求时就返回指定的响应。你可以控制响应的状态码、响应体、响应头等等。一些最佳实践虽然 httpretty 用起来简单但要想用好还是有一些细节需要注意。首先记得在每次测试结束后重置 httpretty。因为 httpretty 是全局状态的如果你在一个测试中注册了一些请求但没有清理可能会影响到其他测试。所以最好在tearDown方法中调用httpretty.reset()。其次尽量让模拟的响应接近真实情况。比如如果真实的 API 返回的是 JSON 数据那么模拟的响应也应该用正确的 JSON 格式并且设置正确的Content-Type头。这样能避免一些隐蔽的问题比如你的代码可能依赖response.json()方法来解析响应如果响应头不对这个方法可能会失败。另外虽然 httpretty 可以模拟任何请求但最好不要滥用它。如果一个测试完全不涉及外部请求那就没必要启用 httpretty。毕竟启用拦截器本身也会带来一点点性能开销。还有一个常见的陷阱是httpretty 默认只拦截它注册过的请求。如果你发起的请求没有匹配到任何注册的规则它会放行这个请求让它真正发出去。这可能会导致测试意外地访问外部服务。为了避免这种情况可以在启用 httpretty 后设置httpretty.HTTPretty.allow_net_connect False这样任何未注册的请求都会抛出异常。和同类技术的对比Python 生态里模拟 HTTP 请求的工具不止 httpretty 一个常见的还有responses、VCR.py、mock库等等。每个工具都有自己的设计哲学和适用场景。responses和 httpretty 很像也是用来模拟请求的但它的 API 设计更贴近requests库。如果你主要用requests可能会觉得responses更顺手一些。不过httpretty 的一个优势是它不局限于requests它能拦截几乎所有基于标准库httplib的请求包括urllib、httplib2等等。VCR.py走的是另一条路。它不让你手动编写模拟的响应而是第一次运行测试时它会记录真实的请求和响应保存到“磁带”文件里以后运行测试就直接从磁带里回放。这种方式特别适合那些已经存在的、依赖大量外部 API 的代码库因为你不必为每个请求手动编写模拟数据。但它的缺点是第一次运行测试还是要访问真实服务而且如果 API 的响应变了你可能需要清理磁带重新录制。至于 Python 标准库里的unittest.mock它当然也可以用来模拟 HTTP 请求但通常是在更高的层次上比如直接替换掉发送请求的函数。这种方式更灵活但需要写更多的样板代码而且容易出错因为你必须确保模拟的对象和真实的对象有一样的接口。总的来说httpretty 在灵活性和易用性之间找到了一个不错的平衡点。它既能精细地控制每个响应的细节又不需要像mock那样写太多样板代码。虽然它可能没有VCR.py那种“一键录制”的便利但对于大多数测试场景来说手动定义响应其实更可控尤其是当你需要测试一些边界条件或错误场景的时候。选择哪个工具最终还是要看具体的需求。如果项目里已经大量使用requests而且测试场景比较简单responses可能就足够了。如果需要支持多种 HTTP 客户端或者想要更底层的控制httpretty 是个不错的选择。而对于那些已经有大量现成测试、想要快速引入 HTTP 模拟的项目VCR.py的录制回放机制可能会节省不少时间。工具只是手段最终目的还是写出可靠、可维护的测试。无论选择哪个工具理解它的工作原理和适用场景才能让它真正为项目服务。

更多文章