pytest-socket 默认报错所有未显式允许的 socket 调用,而非主动禁用网络栈;它通过钩子监控 socket.socket、connect 等标准方法实现拦截,若未加 --disable-socket 参数或配置 addopts,则插件完全不生效。

为什么 pytest-socket 不能直接“禁止”网络访问
pytest-socket 默认行为是**报错所有未显式允许的 socket 调用**,但它本身不主动拦截或禁用网络栈。真正起作用的是它对 socket.socket、socket.connect、socket.send 等底层调用的钩子监控。如果你发现测试仍能发请求,大概率是:没启用插件、被绕过(如用了 urllib.request.urlopen 但没触发 socket 钩子)、或第三方库走的是非标准路径(比如某些 HTTP 库用 ssl.SSLContext 直接握手)。
如何正确启用 pytest-socket 并让其生效
安装后必须显式启用,否则插件完全不工作:
- 命令行运行时加
--disable-socket参数(注意不是--socket-disable) - 或在
pytest.ini中写:[tool:pytest] addopts = --disable-socket
- 若用
pyproject.toml,需写成:[tool.pytest.ini_options] addopts = ["--disable-socket"]
漏掉这个参数,pytest-socket 就像没装一样——这是最常踩的坑。
哪些网络调用会被捕获,哪些容易漏掉
它主要拦截 CPython 标准 socket 模块的公开方法,包括:socket.connect()、socket.send()、socket.recv()、socket.create_connection()。但以下情况通常逃逸:
-
requests.get("http://...")—— 会被捕获(requests 基于 urllib,最终调 socket) -
httpx.get("https://...")—— 多数情况也会被捕获(除非启用了异步 event loop 绕过) -
urllib.request.urlopen()—— 会被捕获 -
subprocess.run(["curl", ...])—— 不会被捕获,因为是独立进程 -
sqlite3.connect("file:memory:")—— 完全无关,不受影响
特别注意:HTTPS 请求可能因 SSL 层提前失败而看不到 socket connect 报错,此时错误信息可能是 ConnectionRefusedError 或空异常,而非明确的 pytest-socket 提示。
如何临时放行特定域名或端口
测试中偶尔需要真实调用(比如验证 mock 是否生效),可用装饰器或 fixture 控制粒度:
- 单个测试函数放行:
@pytest.mark.allow_hosts(["api.example.com"]) def test_something(): pass - 整个模块放行:
import pytest pytestmark = pytest.mark.allow_hosts(["localhost:8000"])
- 用 fixture 临时关闭检查:
@pytest.fixture(autouse=True) def disable_socket_check(request): if "allow_network" in request.keywords: pytest.skip("skipping socket check for this test")(需配合自定义逻辑)
注意 allow_hosts 只接受域名或 host:port 形式,不支持通配符或 IP 段;写成 "*" 会直接失效,不是通配含义。
真正难搞的是那些封装了 socket 的 C 扩展库(比如某些数据库驱动),它们可能跳过 Python 层的 hook。这种时候得靠 pytest-mock 或 unittest.mock.patch 主动 patch 对应的连接方法,而不是依赖 pytest-socket。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











