专栏 :Python 自动化测试从入门到实战
适读人群 :测试工程师 / Python 开发者 / DevOps 入门
预计阅读:13 分钟
一、上接第 6 篇
在 第 6 篇《CI/CD 集成:GitHub Actions》 中,我们解决了:
- GitHub Actions 的 Workflow / Job / Step 三层模型
- pytest 在每次提交后自动运行、Allure 报告归档
- 矩阵策略、缓存、覆盖率与 PR 质量门禁
但有一个隐患没解决:
"在我机器上能跑"------这个经典甩锅,CI 也救不了。
本地 Python 3.12、CI 是 3.11、同事是 3.10;有人装了 redis、有人没装;依赖版本靠 pip freeze 撞大运......
本篇进入 L4 收官阶段 ,用 Docker 回答一个交付问题:
如何让测试环境从"本地 → CI → 线上"完全一致?
答案就是 容器化 ------ 一次构建,处处运行。
二、为什么需要 Docker 化测试环境?
没有容器时,环境靠"口口相传":
| 维度 | 裸机 / venv | Docker 容器 |
|---|---|---|
| 环境一致性 | 各机器差异大 | 镜像即标准,处处一致 |
| 依赖隔离 | venv 只隔离 Python | 系统库、进程、网络全隔离 |
| 复现成本 | 重装半天 | docker pull 即复现 |
| 并发场景 | 端口/数据库冲突 | 独立网络、随起随销毁 |
| 交付物 | 一堆 README | 一个镜像(Image) |
Docker = 把"环境"变成可版本控制的产物
三、核心概念(5 分钟入门)
| 概念 | 含义 |
|---|---|
| Image | 只读模板(环境的快照) |
| Container | Image 的运行实例 |
| Dockerfile | 构建 Image 的脚本(可版本控制) |
| docker-compose | 编排多个容器(应用 + DB + Redis) |
| Volume | 持久化数据(测试结果、缓存) |
| Network | 容器间私有网络 |
一句话 :Dockerfile 定义环境 → docker build 出镜像 → docker run 起容器跑测试。
四、项目结构(沿用专栏体系)
pytest-series/
├── .github/
│ └── workflows/
│ └── test.yml
├── docker/
│ ├── Dockerfile ← 测试镜像(本文核心)
│ └── docker-compose.yml ← 编排测试 + 依赖服务
├── src/
│ └── calculator.py
├── tests/
│ ├── conftest.py
│ ├── test_calculator.py
│ └── test_allure.py
├── requirements.txt
├── pytest.ini
└── README.md
沿用 src/calculator.py + tests/ 结构,Dockerfile 与 compose 放在独立的 docker/ 目录,职责清晰。
五、第一个 Dockerfile:打包测试环境
docker/Dockerfile
dockerfile
# 基础镜像:固定小版本,避免静默升级
FROM python:3.12-slim
# 环境变量:隔离 Python 路径、关闭 pyc 缓存
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/app/.venv/bin:$PATH"
# 系统依赖(如需 mysqlclient / psycopg2 在此装)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 先复制依赖文件,利用 Docker 层缓存
WORKDIR /app
COPY requirements.txt .
RUN python -m venv .venv && \
pip install --no-cache-dir -r requirements.txt
# 复制项目源码
COPY . .
# 默认命令:跑 pytest(可被 docker run 覆盖)
CMD ["pytest", "tests/", "-v"]
要点:
- 固定基础镜像版本 :
python:3.12-slim而非latest,保证可复现 - 依赖分层 :先 COPY
requirements.txt再 COPY 源码,改动代码时不重装依赖,构建快 - 用 venv:与 CI 行为一致,隔离更彻底
CMD可被覆盖 :docker run img pytest tests/test_allure.py临时指定用例
六、构建与本地运行
bash
# 在项目根目录构建(注意末尾的点)
docker build -f docker/Dockerfile -t pytest-series:dev .
# 运行全部测试
docker run --rm pytest-series:dev
# 挂载本地代码,改完即跑(开发态)
docker run --rm -v $(pwd):/app pytest-series:dev
# 只跑某个文件 + 生成 Allure 结果
docker run --rm -v $(pwd)/allure-results:/app/allure-results \
pytest-series:dev pytest tests/ --alluredir=allure-results
关键技巧 :-v 挂载让容器内实时读到本地改动,Allure 报告落到宿主机,无需进容器取。
七、docker-compose:编排测试 + 依赖服务
真实项目测试常依赖 数据库、Redis、Mock 服务,用 compose 一把拉起:
yaml
# docker/docker-compose.yml
version: "3.9"
services:
app:
build:
context: ..
dockerfile: docker/Dockerfile
volumes:
- ../allure-results:/app/allure-results
depends_on:
- postgres
- redis
environment:
- DATABASE_URL=postgresql://test:test@postgres:5432/testdb
- REDIS_URL=redis://redis:6379/0
command: pytest tests/ --alluredir=allure-results
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
启动:docker compose -f docker/docker-compose.yml up --abort-on-container-exit
--abort-on-container-exit:任一容器失败立即整体退出,CI 能正确拿到非零状态码。
八、容器化测试架构图

宿主机 → 挂载代码/结果目录 → 测试容器(pytest + venv)→ 依赖网络 → Postgres / Redis 容器;Allure 结果通过 Volume 落回宿主机。
九、与 CI 结合(承接第 6 篇)
把第 6 篇的 test.yml 改造为在容器内跑测试,实现"本地 = CI":
yaml
# .github/workflows/test.yml(增量)
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -f docker/Dockerfile -t pytest-series:ci .
- name: Run tests in container
run: |
docker run --rm \
-v ${{ github.workspace }}/allure-results:/app/allure-results \
pytest-series:ci pytest tests/ --alluredir=allure-results
- name: Upload Allure report
uses: actions/upload-artifact@v4
with:
name: allure-report
path: allure-results
收益 :CI 不再装 Python、不再 pip install,只负责 build 镜像 + run 容器,环境与本地完全一致。
十、镜像分层与缓存策略
Layer 1: python:3.12-slim ← 基础层(几乎不变)
Layer 2: apt-get + pip venv ← 系统/依赖层(偶尔变)
Layer 3: COPY requirements.txt ← 依赖声明(变则重建)
Layer 4: COPY . . ← 源码层(每次变)
优化原则 :越稳定的内容越靠上 ,最大化复用缓存。构建耗时通常降低 50%~80%。
💡 进阶:用 多阶段构建(build 阶段编译、runtime 阶段只留产物)进一步缩小镜像体积。
十一、镜像体积与安全最佳实践
| 实践 | 做法 |
|---|---|
| 用 slim/alpine 基础镜像 | 从 ~1GB 降到 ~150MB |
| 多阶段构建 | 丢弃编译工具链 |
| 非 root 运行 | USER 1000 避免权限过大 |
| 固定版本标签 | 不用 latest |
| 扫描漏洞 | docker scout / Trivy |
.dockerignore |
排除 .git、.venv、报告等 |
十二、数据卷与结果持久化
测试结果必须落回宿主机,否则容器销毁即丢失:
yaml
volumes:
- ../allure-results:/app/allure-results # Allure 报告
- ../coverage.xml:/app/coverage.xml # 覆盖率
- pytest-cache:/app/.pytest_cache # 缓存(命名卷,跨运行复用)
命名卷 vs 绑定挂载:
- 绑定挂载 (
-v 绝对路径):方便宿主机直接读,适合报告 - 命名卷:由 Docker 管理,适合缓存,生命周期独立于容器
十三、环境一致性验证(三处同一)
为确保"本地 = CI = 线上",验证三处使用同一镜像:
| 场景 | 命令 |
|---|---|
| 本地开发 | docker run --rm -v $(pwd):/app img |
| CI | docker run --rm img(GitHub Actions) |
| 线上/预发 | docker run --rm img pytest tests/ --cov |
只要镜像哈希一致,环境就一致 ------ 这就是容器化的核心价值。
十四、完整 Dockerfile + compose 一览
dockerfile
# docker/Dockerfile(生产级)
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN python -m venv .venv && \
.venv/bin/pip install --no-cache-dir -r requirements.txt
COPY --chown=1000:1000 . .
USER 1000
CMD ["pytest", "tests/", "-v"]
compose 见 第七节,两者配合即构成完整交付单元。
十五、常见坑速查
| 问题 | 原因 | 解决 |
|---|---|---|
| 容器内连不上 DB | 用了 localhost |
改用服务名 postgres |
| 挂载后权限拒绝 | 宿主机 UID 不匹配 | USER 1000 + 目录授权 |
| 依赖装了却找不到 | 没用 venv 的 pip | 用 .venv/bin/pip |
| 报告没生成 | 没挂载 Volume | -v 挂 allure-results |
| 构建越来越慢 | 层顺序不当 | 稳定内容靠上 |
十六、小结
✅ Docker 化 = 把"环境"变成可版本控制的镜像
✅ Dockerfile 分层:稳定的内容靠上 ,最大化缓存复用
✅ docker-compose 编排 app + DB + Redis ,一键起停
✅ Volume 保证 Allure/覆盖率结果持久化
✅ CI 只负责 build + run,本地 = CI = 线上一致性交付
十七、下一篇预告
👉 《从零搭建生产级测试项目:专栏收官实战》
你将学到:
- 把 L1~L4 全部知识整合为一个完整项目
- 测试分层、目录规范、CI/CD 全流程
- 可复用的项目脚手架与最佳实践清单