Docker与Python单元测试结合最佳实践:如何排除测试依赖且保障环境一致
核心解决思路
核心通过两个方向解决问题:1. 锁死全量依赖版本,彻底消除安装测试依赖导致子依赖版本变动的问题;2. 用多阶段Docker构建将测试环节前置到生产镜像生成前,同时保证两个阶段的运行时环境完全一致。
具体实现方案
1. 依赖拆分与版本锁定
- 把项目依赖拆为运行时依赖和测试/开发依赖两类,可通过
pyproject.toml的可选依赖分组,或分开的requirements文件实现 - 用依赖锁定工具生成全量版本锁文件:比如Poetry的
poetry.lock、pip-tools的锁文件,锁文件会记录所有子依赖的精确版本,安装时会严格遵循锁文件的版本,不会因为额外安装测试依赖变更运行时依赖的版本
你提到的rasterio和pytest都依赖attrs的场景,只要锁文件里固定了attrs的版本,不管是单独装运行时依赖还是同时装测试依赖,attrs的版本都会和锁文件完全一致,不会出现版本变动。
如果用普通requirements管理依赖,可按如下规则拆分:
requirements.txt:存储运行时依赖requirements-dev.txt:存储测试依赖(首行加-r requirements.txt,再补充pytest等测试工具)- 用
pip-compile生成全量锁文件requirements-lock.txt和requirements-dev-lock.txt即可
2. 多阶段Docker构建配置
把镜像构建分为两个阶段:测试验证阶段、生产镜像生成阶段,不需要手动维护任何子依赖列表,示例Dockerfile如下(Poetry场景):
# 测试验证阶段:包含完整依赖,用于执行单元测试 FROM python:3.11-slim AS test-stage WORKDIR /app # 先拷贝依赖文件,利用Docker缓存加速构建 COPY pyproject.toml poetry.lock ./ RUN pip install --no-cache-dir poetry \ && poetry install --no-root # 安装运行时+所有测试/开发依赖 # 拷贝业务代码 COPY . . # 执行单元测试,测试失败会直接中断构建流程,不会进入后续生产镜像生成阶段 RUN poetry run pytest # 生产镜像阶段:仅包含运行时依赖,无任何测试相关内容 FROM python:3.11-slim AS production WORKDIR /app COPY pyproject.toml poetry.lock ./ # 仅安装运行时依赖,版本和锁文件完全一致,和测试阶段的运行时依赖版本100%匹配 RUN pip install --no-cache-dir poetry \ && poetry install --no-root --without dev # 从测试阶段直接拷贝已经过测试验证的业务代码,避免二次拷贝引入差异 COPY --from=test-stage /app/src ./src # 配置启动命令 CMD ["python", "src/main.py"]
如果是requirements场景,替换对应安装命令即可:
- 测试阶段:
RUN pip install --no-cache-dir -r requirements-dev-lock.txt - 生产阶段:
RUN pip install --no-cache-dir -r requirements-lock.txt
3. CI工作流调整
不需要再单独启动容器执行测试,直接调整CI步骤为:
- 执行
docker build -t my-app .,构建过程中会自动执行测试,测试失败直接终止CI流程 - 测试通过后,给最终生成的production阶段镜像打标签,推送到镜像仓库即可
如果需要单独跑测试,也可以单独构建测试阶段:docker build --target test-stage .
内容的提问来源于stack exchange,提问作者benjimin
相关产品推荐
相关产品推荐

