用 pytest-socket 禁用网络访问:在 pytest.ini 中添加 --disable-socket

举报
yd_229231784 发表于 2026/10/25 00:15:41 2026/10/25
【摘要】 用 pytest-socket 在 pytest.ini 中配置 --disable-socket,可让单元测试默认无法建立网络连接,避免意外调用真实 HTTP 接口。本文给出可运行示例、运行方法与边界限制,并说明如何对 HTTP 客户端打桩及按需放行本地服务。

用 pytest-socket 禁用网络访问:在 pytest.ini 中添加 --disable-socket

问题场景

单元测试应当快速、稳定、可重复。但有些代码在测试中会意外触发真实 HTTP 请求:例如忘记替换客户端、依赖初始化时探测远程配置、缓存未命中时回源。这类测试依赖外部服务可用性、网络延迟和远端数据,容易在本地通过、在 CI 失败,或者污染真实数据。即使测试当前通过,也可能在无网络环境下随机失败。

一种低成本护栏是:让测试进程默认无法建立网络连接。pytest-socket 提供了 --disable-socket 选项,可以在 pytest.ini 中全局开启,使意外网络访问立即失败,从而暴露未打桩的依赖。

安装与最小配置

安装:

pip install pytest pytest-socket

在项目根目录的 pytest.ini 中:

[pytest]
addopts = --disable-socket

这样运行 pytest 时默认禁用 socket 连接。若已有 addopts,可合并,例如 addopts = -q --disable-socket。

可运行示例

假设被测模块会调用 HTTP 接口:

# app.py
import json
import urllib.request

def get_user_name(base_url: str, user_id: str) -> str:
    url = f'{base_url}/users/{user_id}'
    with urllib.request.urlopen(url, timeout=3) as resp:
        data = json.loads(resp.read().decode('utf-8'))
    return data['name']

单元测试中用 monkeypatch 替换 urllib.request.urlopen,避免真实网络:

# test_app.py
import json
from app import get_user_name

class FakeResponse:
    def __init__(self, payload: bytes):
        self.payload = payload

    def read(self) -> bytes:
        return self.payload

    def __enter__(self):
        return self

    def __exit__(self, *args):
        return False

def test_get_user_name(monkeypatch):
    payload = json.dumps({'name': 'alice'}).encode('utf-8')

    def fake_urlopen(url, timeout=3):
        assert url == 'http://api.example.invalid/users/123'
        assert timeout == 3
        return FakeResponse(payload)

    monkeypatch.setattr('app.urllib.request.urlopen', fake_urlopen)
    assert get_user_name('http://api.example.invalid', '123') == 'alice'

再增加一个护栏测试,确认禁用生效:

# test_guard.py
import socket
import pytest
import pytest_socket

def test_socket_is_disabled():
    with pytest.raises(pytest_socket.SocketBlockedError):
        socket.create_connection(('127.0.0.1', 9), timeout=0.1)

运行:

python -m pytest -q

预期两个测试通过。如果删除 monkeypatch 逻辑,让 test_app 直接调用真实 URL,测试会因 SocketBlockedError 失败。这正是隔离护栏的作用:把“意外访问网络”从难以察觉的偶发问题,变成明确失败。

边界与限制

--disable-socket 主要拦截 Python socket 层。它不能替代进程级或容器级网络隔离,也不能阻止子进程、C 扩展直接发起系统调用、已经建立的连接,或非 socket 的进程间通信。因此,它适合作为单元测试护栏,不适合作为安全边界。

若确实需要访问本地服务,应尽量缩小放行范围,例如使用 --allow-hosts=127.0.0.1,localhost,或在单个测试上使用 @pytest.mark.enable_socket。但放行后测试不再完全隔离,应明确标记为集成测试,并与默认单元测试分开运行。

异步 HTTP 客户端最终通常仍经过 socket,因此一般会被拦截,但异常可能被客户端包装,断言时建议针对业务行为而不是异常链。不同 pytest 与 pytest-socket 版本对选项、异常类的支持可能不同,建议在 CI 中固定依赖版本,并用 pytest --help 确认可用选项。

最后,禁用网络只是护栏,不是 mock 的替代。外部 HTTP 客户端、数据库、消息队列等仍需在测试中替换为受控的假实现或测试替身。

小结

在 pytest.ini 中加入 addopts = --disable-socket,可以用极低配置成本防止单元测试意外访问真实 HTTP 接口,提升测试隔离和可重复性。配合 monkeypatch 等打桩手段,并合理处理本地服务放行与集成测试边界,可以让测试套件更稳定、更可信。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。