环境搭建:Python版本选择、虚拟环境创建、依赖管理、asyncio库版本兼容性

做CI异步测试,环境搭建是第一步,也是最容易踩坑的一步。今天我会从实战角度,把Python版本、虚拟环境、依赖管理、asyncio兼容性这些事儿一次讲透。你想想看,如果环境没搭好,后面再怎么调试都是白费力气。

CI异步测试环境搭建核心流程 Python版本选择 虚拟环境创建 依赖管理 asyncio版本兼容性 环境验证 & 测试运行 关键:版本选错 → 虚拟环境隔离不彻底 → 依赖冲突 → asyncio跑不起来 → CI直接挂掉

Python版本选择:别用最新,也别用最旧

做CI异步测试,Python版本怎么挑?我个人的习惯是——看你的项目跑在哪个CI平台上。GitHub Actions的默认虚拟机、GitLab Runner、Jenkins节点,它们的系统镜像里预装了哪些Python版本,心里要有数。

举个例子,我记得2023年有个项目,直接用了Python 3.12,结果CI环境只支持到3.11,部署上去全崩了。后来我学乖了,统一用3.10或3.11。

建议版本范围:

  1. Python 3.8~3.11:首选。asyncio在3.8引入了一些关键修正,3.10/3.11表现最稳定。
  2. Python 3.12:慎用。部分第三方库还没完全适配,特别是异步协程相关的绑定库。
  3. Python 3.7以下:别碰。asyncio的API不全,而且很多CI平台已经不再支持。

真有特殊需要,我建议在项目根目录放一个.python-version文件,或者用pyproject.toml明确指定。CI脚本里也写死版本,别用python3这种软链接。

为什么会这样?说白了就是版本兼容性问题。CI环境里预装的Python版本经常比你本地的低,你不显式指定,等着翻车吧。

虚拟环境创建:隔离是第一要务

虚拟环境,我一般用venv。轻量、内置、不依赖第三方。虽然conda也可以,但CI环境下装miniconda太慢了。

创建命令很简单,但有坑。

python3 -m venv .venv
source .venv/bin/activate

嗯,这里要注意:CI脚本里不要用source,而是直接用路径的Python解释器,或者用./.venv/bin/python调用。我曾经在GitLab CI里用了source activate,结果下一行脚本的环境变量没继承,依赖全装到系统里去了……

CI环境下虚拟环境推荐做法:

  • 在CI配置的before_script阶段创建环境
  • 用pip install -r requirements.txt直接装在虚拟环境里
  • 每次CI runner都从零开始创建,避免缓存污染
  • 如果依赖下载慢,可以用pip cache但小心版本锁定

我个人还有个习惯:把虚拟环境目录.venv加到.gitignore里,然后CI脚本里重新创建。这样本地环境和CI环境保持一致。

依赖管理:锁定版本,别用通配符

依赖管理,核心就一句话——锁定所有依赖的精确版本。别写requests>=2.28这种范围,你永远不知道CI环境下pip会给你装哪个版本。

推荐用pip freeze > requirements.txt生成锁文件。但是要注意,asyncio相关库的依赖树可能比较复杂。

包名 建议版本 说明
pytest-asyncio ≥0.21, <0.24 0.24改了测试收集模式,小心兼容
aiohttp 3.8, 3.9 3.9开始强制要求Python 3.8+
httpx 0.24, 0.27 async支持完整,但注意event loop策略
anyio 3.6, 4.0 如果你同时支持trio和asyncio

我曾经踩过一个坑:某次升级了aiohttp到4.0,结果asyncio的event loop在CI上莫名其妙报RuntimeError,折腾了半天,发现是底层依赖yarl不兼容。从那以后,我每次改依赖都会跑一遍pip check。

# 推荐的锁定流程
pip install -e ".[dev]"
pip freeze --exclude-editable > requirements.txt
pip check  # 确保没有冲突

警告:如果项目使用了setup.py或pyproject.toml的动态版本,pip freeze可能会输出本地路径。建议用--exclude-editable参数,或者用pip list --format=freeze。

asyncio库版本兼容性:那些年我们追过的API变化

asyncio本身是Python标准库,版本跟随Python一起更新。但很多第三方异步库对asyncio的版本有隐式要求。

我列出几个实际项目中常见的兼容性问题:

  • Python 3.8:asyncio.run()支持debug=True参数,但loop.create_task()还不支持name参数
  • Python 3.9:新增了TaskGroup(不过严格来说是3.11才稳定)
  • Python 3.10:asyncio.get_running_loop()行为更严格,很多旧代码会报错
  • Python 3.11:TaskGroup和Timeout上下文管理器正式可用

你的异步测试代码如果用了asyncio.wait_for、asyncio.gather,基本没大问题。但如果你用了loop对象操作(比如loop.run_until_complete),在Python 3.10+上会被警告甚至移除。

我在项目中验证兼容性的方法:

# 在CI脚本中加入这段检查
import sys
import asyncio
print(f"Python {sys.version}")
print(f"asyncio {asyncio.__version__ if hasattr(asyncio, '__version__') else 'builtin'}")

# 测试基本协程
async def test():
    await asyncio.sleep(0.1)
    return "ok"
result = asyncio.run(test())
assert result == "ok"

为什么要在CI里加这个?因为不同Python小版本的asyncio实现细节不同,比如3.7和3.8的run()实现就完全不同。你想想看,本地跑得好好的,CI上报个RuntimeError: Event loop is closed,多郁闷。

我个人还会在requirements.txt里加上pytest-asyncio时指定版本范围,因为pytest-asyncio的版本和asyncio的绑定很紧。0.21版本支持@pytest.mark.asyncio装饰器,但0.23改了默认loop作用域。我曾经因为升级了pytest-asyncio,导致所有异步测试都收集不到,CI全红。

最终推荐的环境组合:

Python版本3.10.11 或 3.11.5
虚拟环境工具venv (Python内置)
依赖管理pip + requirements.txt (锁死版本)
asyncio测试框架pytest-asyncio 0.21.1
兼容性验证CI脚本中运行asyncio基本检查

嗯,环境搭建这个东西,看似基础,但每一条背后都有血泪教训。我做的《CI异步测试任务实战指南》后续的所有内容,都基于这个环境。所以今天花20分钟搭好环境,后面能省下你一整天调试时间。