aiohttp 移除 aiohttp.pytest_plugin:全面迁移到 pytest-aiohttp 的破坏性变更指南

aiohttp 移除 aiohttp.pytest_plugin:全面迁移到 pytest-aiohttp 的破坏性变更指南

【免费下载链接】aiohttp Asynchronous HTTP client/server framework for asyncio and Python 【免费下载链接】aiohttp 项目地址: https://gitcode.com/gh_mirrors/ai/aiohttp

变更记录:aiohttp 在其变更日志中正式移除 aiohttp.pytest_plugin,并明确要求用户改用独立的 pytest-aiohttp 插件。本文以该破坏性变更为核心,梳理变更的前因后果、迁移步骤、fixture 对照以及仓库内真实用法,帮助你在升级 aiohttp 后快速修复测试基础设施。

一、这则 breaking change 说了什么

在 CHANGES/10762.breaking.rst 中,aiohttp 记录了一条典型的破坏性变更:

Removed aiohttp.pytest_plugin. Users should use pytest-aiohttp instead.

翻译过来就是:aiohttp 内置在库内的 pytest 插件入口已被移除,用户应改用独立的 pytest-aiohttp 包提供的插件。这意味着升级到包含该变更的 aiohttp 版本后,凡是仍然通过 aiohttp.pytest_plugin 方式加载或引用插件的项目,都会遇到 ModuleNotFoundError 或插件无法加载的问题,必须在升级过程中同步完成迁移。

需要特别说明的是,这不是 aiohttp 测试能力的整体移除。被移除的只是“随 aiohttp 库捆绑的 pytest 插件封装”,aiohttp 提供的测试工具本体(如 TestServer、TestClient、make_mocked_request 等,位于 aiohttp/test_utils.py)依然保留,只是这些工具与 pytest 之间的“接线层”被移交给了独立的 pytest-aiohttp 项目。

二、历史脉络:从内置插件到独立生态

结合 CHANGES.rst 可以完整还原这条变更的演进轨迹:

  1. pytest 插件随库内置:aiohttp 最初通过变更记录中的 #914 引入 pytest_plugin,让用户开箱即用地获得 pytest fixture 支持。
  2. 提供静态类型支持:后续变更为 aiohttp.pytest_plugin.AiohttpClient 提供静态类型标注,方便在类型检查环境下使用。
  3. 文档转向 pytest-aiohttp 命名空间:aiohttp 在文档中改为提供 pytest-aiohttp 命名空间来承载 fixtures,文档页 docs/testing.rst 中已明确写出“For using pytest plugin please install pytest-aiohttp library”。
  4. 命令行入口规范化:pytest-aiohttp 插件为其命令行参数添加了 aiohttp- 前缀。
  5. 弃用并最终移除:在 CHANGES.rst 的 Deprecations 章节中,aiohttp 为 aiohttp.pytest_plugin 添加了弃用警告,要求用户切换到 pytest-aiohttp(对应 issue 10785);随后便是本文主题 CHANGES/10762.breaking.rst 中的正式移除。

从这条路径可以看出,aiohttp 的长期策略是:把 pytest 集成从核心库里解耦出来,交给专门的 pytest-aiohttp 项目独立维护。这样 aiohttp 自身的发版节奏、Python 版本兼容策略不再被 pytest 插件的细节所牵制,而插件本身也能更快适配 pytest 的新特性。

三、迁移前:确认你的项目是否受影响

判断是否受该变更影响,可以从以下三个角度自查:

  • 依赖层面:是否曾在 requirements、setup.py/pyproject.toml 中直接或间接依赖了 aiohttp.pytest_plugin 这个模块路径。
  • 导入层面:测试代码或 conftest.py 中是否存在 import aiohttp.pytest_plugin 或 pytest_plugins = ("aiohttp.pytest_plugin",) 之类的写法。
  • 配置层面:pytest.ini、pyproject.toml 的 [tool.pytest.ini_options] 或 setup.cfg 中是否通过 -p aiohttp.pytest_plugin 方式预加载插件。

只要命中任意一条,升级 aiohttp 后就需要迁移。如果此前一直在使用官方文档推荐的 pytest-aiohttp 独立包(即通过 pip install pytest-aiohttp 安装后依赖其 entry point 自动注册插件),那么你的项目不受影响,可以跳过迁移步骤。

四、迁移步骤:切换到 pytest-aiohttp

4.1 安装独立插件

在测试依赖中安装 pytest-aiohttp。aiohttp 仓库自身的依赖锁定文件可以作为参考,例如 requirements/constraints.txt、requirements/test-common-base.txt 中都锁定了 pytest-aiohttp==1.1.1,说明这是 aiohttp 自身测试所验证过的版本:

pip install pytest-aiohttp

也可以将其写入你的测试依赖声明(例如 requirements-dev.txt 或 pyproject.toml 的 optional-dependencies)。

4.2 配置 asyncio 模式

pytest-aiohttp 的异步 fixture 需要 pytest 运行在 asyncio 模式之下。aiohttp 官方文档 docs/testing.rst 明确要求,需要在使用 --asyncio-mode=auto 或直接在 pytest 配置文件中写入:

asyncio_mode = auto

aiohttp 自己的 pytest.ini 正是这样配置的(第 44 行 asyncio_mode = auto),这也是它在整个测试套件中无需为每个用例手动打异步标记的原因。迁移时请务必将这一行加入你的 pytest 配置,否则异步 fixture 和异步测试函数将无法正常收集执行。

4.3 替换插件加载方式

  • 若你的 conftest.py 或测试模块中写了 pytest_plugins = ("aiohttp.pytest_plugin",),应删除该声明——pytest-aiohttp 作为独立包会通过 setuptools entry point 自动注册,无需手动声明。
  • 若你在命令行或 addopts 中写了 -p aiohttp.pytest_plugin,请改为 -p pytest_aiohttp.plugin 或直接删除(依赖自动注册)。

aiohttp 自身的测试套件提供了一个很好的参照:在 tests/conftest.py 第 78-79 行,其根 conftest 明确写着:

# We require pytest-aiohttp to avoid confusing debugging if it's not installed.
pytest_plugins = ("pytest_aiohttp.plugin", "pytester")

注意这里引用的是 pytest_aiohttp.plugin 这个新模块路径,注释也点明了意图——如果没安装 pytest-aiohttp,测试会因 import 失败而直接暴露问题,而不是在运行中途出现令人困惑的调试困境。这也说明:迁移后的正确姿势是把 pytest-aiohttp 当作项目测试依赖的一部分,而非可选项。

4.4 替换模块导入路径

在类型标注场景下,旧写法是从 aiohttp.pytest_plugin 导入类型别名;迁移后应从 pytest_aiohttp 导入。aiohttp 测试代码中的典型用法(见 tests/test_client_functional.py 第 41 行):

from pytest_aiohttp import AiohttpClient, AiohttpServer

然后在测试函数签名中用作注解:

async def test_keepalive_two_requests_success(aiohttp_client: AiohttpClient) -> None:
    ...

AiohttpClient 与 AiohttpServer 分别是 aiohttp_client、aiohttp_server 两个 fixture 工厂的静态类型描述,供类型检查工具和 IDE 使用。整个 aiohttp 测试套件(如 tests/test_client_session.py、tests/test_connector.py、tests/test_web_app.py 等数十个测试文件)均统一采用 from pytest_aiohttp import AiohttpClient, AiohttpServer 的导入方式,可作为大规模迁移时的机械替换范本。

五、fixture 对照:迁移后你仍然拥有这些能力

迁移到 pytest-aiohttp 后,官方文档 docs/testing.rst 中定义的 fixture 全部可用,且用法与内置插件时代保持一致:

Fixture作用关键参数
aiohttp_client(app/server, server_kwargs=None, **kwargs)创建 TestClient,向被测服务器发起请求接受 web.Application、TestServer 或 RawTestServer;server_kwargs 在传 app 时透传给测试服务器;kwargs 传给 TestClient 构造器
aiohttp_server(app, *, port=None, **kwargs)启动基于 web.Application 的 TestServerport 缺省时使用随机空闲端口;kwargs 传给 web.AppRunner
aiohttp_raw_server(handler, *, port=None, **kwargs)基于低级 web.Server handler 启动 RawTestServerhandler 是接收请求、返回响应的协程
aiohttp_client_cls注入自定义 TestClient 子类可通过 request.node.get_closest_marker(...) 按测试标记切换客户端实现

一个典型的测试用例(摘自 docs/testing.rst):

from aiohttp import web

async def hello(request):
    return web.Response(text='Hello, world')

async def test_hello(aiohttp_client):
    app = web.Application()
    app.router.add_get('/', hello)
    client = await aiohttp_client(app)
    resp = await client.get('/')
    assert resp.status == 200
    text = await resp.text()
    assert 'Hello, world' in text

还可以借助 pytest 自身的 fixture 机制封装出更精简的测试入口:

import pytest
from aiohttp import web

@pytest.fixture
async def cli(aiohttp_client):
    app = web.Application()
    app.router.add_get('/', hello)
    app.router.add_post('/', previous)
    return await aiohttp_client(app)

async def test_set_value(cli):
    resp = await cli.post('/', data={'value': 'foo'})
    assert resp.status == 200
    assert await resp.text() == 'thanks for the data'
    assert cli.server.app[value] == 'foo'

六、受影响之外的测试资产:aiohttp.test_utils 不受影响

本次移除不涉及 aiohttp.test_utils 模块。从 aiohttp/test_utils.py 的类定义可以看出,框架无关的测试工具依然完整保留:

  • BaseTestServer / TestServer / RawTestServer:在随机 TCP 端口上运行被测服务;TestServer 承载 web.Application,RawTestServer 承载低级 web.Server handler。
  • TestClient:封装 ClientSession,提供 get、post、put、delete、ws_connect 等代理方法与 make_url()、start_server()、close() 等生命周期方法。
  • AioHTTPTestCase:基于 unittest.IsolatedAsyncioTestCase 的 unittest 风格测试基类,供标准库 unittest 用户使用,可通过覆写 get_application() 返回被测应用。
  • make_mocked_request:在不启动真实服务器的情况下构造 web.Request 模拟对象,适合单测 handler 或模拟难以复现的网络错误场景。

这些工具可以直接在 asyncio.run 或任何测试框架中手动组装使用(文档中称之为“framework agnostic utilities”),例如:

from aiohttp.test_utils import TestClient, TestServer

async def test():
    app = _create_example_app()
    async with TestClient(TestServer(app)) as client:
        resp = await client.get("/")
        assert resp.status == 200

换句话说,pytest-aiohttp 的 fixtures 只是这些工具的 pytest 封装;迁移只影响“封装层”,不触碰“工具层”。如果项目里同时使用 unittest 风格测试,那部分代码完全不需要改动。

七、仓库自证:aiohttp 自身就是这么做的

aiohttp 的整个测试套件本身就是这次迁移的“活体样例”,可以作为迁移后的验收模板:

  • 插件加载:tests/conftest.py 通过 pytest_plugins = ("pytest_aiohttp.plugin", "pytester") 显式加载 pytest_aiohttp.plugin,并在注释中强调必须安装 pytest-aiohttp,避免“没装插件却只报奇怪错误”的排查困境。
  • 类型导入:数十个测试文件统一使用 from pytest_aiohttp import AiohttpClient, AiohttpServer,例如 tests/test_client_functional.py、tests/test_proxy_functional.py(后者还使用了 AiohttpRawServer)。
  • 依赖锁定:pytest-aiohttp==1.1.1 出现在 requirements/test-common-base.in 等多个依赖清单中,并在 lock 文件里被固定版本。
  • 运行配置:pytest.ini 中的 asyncio_mode = auto 是异步 fixture 正常工作的前提。

如果你要验证自己的项目迁移是否彻底,可以做一个简单的全量搜索:确认代码库中已不存在 aiohttp.pytest_plugin(含 aiohttp.pytest_plugin.AiohttpClient 等子路径)的引用,同时确认 pytest-aiohttp 出现在测试依赖声明中,且 pytest 配置里已设置 asyncio_mode = auto。

八、常见迁移问题排查

  • 升级后 pytest 报插件加载失败:通常是 -p aiohttp.pytest_plugin 或 pytest_plugins = ("aiohttp.pytest_plugin",) 残留所致,按 4.3 节清理即可。
  • fixture aiohttp_client 未找到:确认 pytest-aiohttp 已安装且未被 -p no:pytest_aiohttp.plugin 之类的方式禁用。
  • 异步测试不执行或报“coroutine was never awaited”:确认 pytest 配置中存在 asyncio_mode = auto(见 4.2 节)。
  • 类型检查报 AiohttpClient 无法导入:将导入源从 aiohttp.pytest_plugin 改为 pytest_aiohttp(见 4.4 节)。
  • unittest 风格测试是否受影响:不受影响,aiohttp.test_utils.AioHTTPTestCase 继续可用(见第六节)。

总结

aiohttp.pytest_plugin 的移除是 aiohttp 将 pytest 生态解耦的重要一步:测试工具本体(aiohttp.test_utils)继续留在核心库中演进,而 pytest 的接线层统一由 pytest-aiohttp 承担。对使用者而言,迁移路径清晰:安装 pytest-aiohttp、配置 asyncio_mode = auto、清理旧插件加载与导入路径。aiohttp 自身的测试套件(tests/conftest.py、tests/test_client_functional.py 等)就是迁移后的最佳参照实现,官方文档 docs/testing.rst 则提供了完整的 fixtures 与工具参考,可作为迁移完成后核对行为的最终依据。

【免费下载链接】aiohttp Asynchronous HTTP client/server framework for asyncio and Python 【免费下载链接】aiohttp 项目地址: https://gitcode.com/gh_mirrors/ai/aiohttp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值