11-测试与部署
Python 3.11+
本章讲解 Flask 应用测试和生产部署。
第一部分:Flask 测试客户端
1.1 实际场景
你需要验证 API 是否正常工作,测试登录功能、注册功能等。
问题:如何编写自动化测试验证应用功能?
1.2 基本用法
Flask 提供了测试客户端,可以模拟请求测试应用。
python
import pytest
from flask import Flask
from flask.testing import FlaskClient
@pytest.fixture
def app() -> Flask:
app: Flask = create_app("testing")
with app.app_context():
yield app
@pytest.fixture
def client(app: Flask) -> FlaskClient:
return app.test_client()
def test_index(client: FlaskClient) -> None:
"""测试首页"""
response = client.get("/")
assert response.status_code == 200
def test_login(client: FlaskClient) -> None:
"""测试登录"""
response = client.post("/auth/login", data={
"username": "admin",
"password": "admin123"
})
assert response.status_code == 302 # 重定向1.3 测试 JSON API
python
from typing import Any
def test_api_create_user(client: FlaskClient) -> None:
"""测试创建用户 API"""
response = client.post("/api/users", json={
"username": "testuser",
"email": "test@example.com"
})
assert response.status_code == 201
data: dict[str, Any] = response.get_json()
assert data["username"] == "testuser"第二部分:pytest 配置
2.1 实际场景
测试需要数据库初始化、配置加载等准备工作。
问题:如何配置测试环境和 fixtures?
2.2 pytest.ini
ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*2.3 conftest.py
python
import pytest
from flask import Flask
from flask.testing import FlaskClient, FlaskCliRunner
from app import create_app, db
@pytest.fixture(scope="session")
def app() -> Flask:
app: Flask = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.drop_all()
@pytest.fixture
def client(app: Flask) -> FlaskClient:
return app.test_client()
@pytest.fixture
def runner(app: Flask) -> FlaskCliRunner:
return app.test_cli_runner()第三部分:生产部署
3.1 实际场景
开发完成后,需要将应用部署到生产服务器。
问题:如何部署 Flask 应用到生产环境?
3.2 Gunicorn
bash
# 安装
uv add gunicorn
# 运行
uv run gunicorn -w 4 -b 0.0.0.0:8000 app:app3.3 Docker 部署
Dockerfile:
dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]docker-compose.yml:
yaml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/app
depends_on:
- db
- redis
db:
image: postgres:15
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
postgres_data:3.4 Nginx 配置
nginx
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
alias /app/static;
}
}第四部分:L3 专家层
6.1 pytest fixture 的作用域链
pytest fixture 通过作用域控制资源的创建和销毁时机,理解作用域链对测试性能和隔离性至关重要。
作用域层级
+---------------------------------------------------------------+
| Fixture 作用域链 |
+---------------------------------------------------------------+
| |
| session (整个测试会话只创建一次) |
| └── package (每个 Python 包创建一次) |
| └── module (每个测试文件创建一次) |
| └── class (每个测试类创建一次) |
| └── function (每个测试函数创建一次,默认) |
| |
| 外层 fixture 可被内层 fixture 依赖 |
| 内层 fixture 不能依赖外层以外的 fixture |
| |
+---------------------------------------------------------------+python
import pytest
from flask import Flask
from flask.testing import FlaskClient
from typing import Generator
# session 级:整个测试会话共享
# 适用:创建数据库引擎、加载配置
@pytest.fixture(scope="session")
def app_factory() -> Flask:
print("session setup: 创建应用实例")
app: Flask = create_app("testing")
yield app
print("session teardown: 清理应用")
# module 级:每个测试文件共享
# 适用:创建测试数据库表
@pytest.fixture(scope="module")
def db(app_factory: Flask) -> Generator[None, None, None]:
print("module setup: 创建数据库表")
with app_factory.app_context():
db.create_all()
yield
db.drop_all()
print("module teardown: 删除数据库表")
# function 级(默认):每个测试独立
# 适用:隔离测试数据
@pytest.fixture
def client(app_factory: Flask, db: None) -> FlaskClient:
return app_factory.test_client()
# 依赖链:client → db → app_factory
# 执行顺序:
# 1. session: app_factory setup
# 2. module: db setup
# 3. function: client setup
# 4. 运行测试
# 5. function: client teardown
# 6. module: db teardown
# 7. session: app_factory teardown性能考量
| 作用域 | 创建次数 | 隔离性 | 性能 | 适用场景 |
|---|---|---|---|---|
| session | 1 次 | 无 | 最好 | 配置加载、数据库连接池 |
| module | 每个文件 1 次 | 文件级 | 好 | 数据库表结构、共享数据 |
| class | 每个类 1 次 | 类级 | 中 | 同一业务逻辑的多个测试 |
| function | 每个测试 1 次 | 完全隔离 | 最差 | 状态敏感的操作(认证、数据变更) |
设计动机
| 设计 | 解决的问题 |
|---|---|
| 作用域分层 | 平衡测试隔离性与执行速度 |
| 外层可被内层依赖 | 资源复用:DB 引擎只创建一次,每次测试复用连接 |
| 自动 teardown | 防止测试间状态泄漏(脏数据) |
scope="function" 默认 | 安全优先:新开发者写测试天然隔离,不易踩坑 |
6.2 Gunicorn 的 Worker 模型
Gunicorn 支持多种 Worker 类型,选择取决于应用的 I/O 特征。
Worker 架构
+-----------------------------------------------------------------+
| Gunicorn Master Process |
| |
| ┌────────────────────────────────────────────────────────────┐ |
| │ Master (管理进程) │ |
| │ - 监听信号 (HUP/TERM/USR2) │ |
| │ - 监控 Worker 健康 │ |
| │ - 动态扩缩容 │ |
| └────────────────────────┬──────────────────────────────────┘ |
| │ fork() |
| ┌────────────────┼────────────────┐ |
| ▼ ▼ ▼ |
| ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ |
| │ Worker 1 │ │ Worker 2 │ │ Worker N │ |
| │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ |
| │ │ Thread 1 │ │ │ │ Thread 1 │ │ │ │ Thread 1 │ │ |
| │ │ Thread 2 │ │ │ │ Thread 2 │ │ │ │ Thread 2 │ │ |
| │ │ ... │ │ │ │ ... │ │ │ │ ... │ │ |
| │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ |
| │ (gthread) │ │ (gthread) │ │ (gthread) │ |
| └──────────────┘ └──────────────┘ └──────────────┘ |
+-----------------------------------------------------------------+Worker 类型对比
python
# sync: 同步 Worker(默认)
# 每个 Worker 同时只能处理一个请求
# gunicorn -w 4 -k sync app:app
#
# 适用:CPU 密集型、无外部 I/O 等待
# 公式:workers = 2 * CPU核心数 + 1
# gthread: 多线程 Worker
# 每个 Worker 有线程池,可同时处理多个请求
# gunicorn -w 4 -k gthread --threads 4 app:app
#
# 适用:少量 I/O 等待的混合场景
# eventlet / gevent: 协程 Worker
# 每个 Worker 使用协程(greenlet),可并发处理数百请求
# gunicorn -w 4 -k gevent --worker-connections 1000 app:app
#
# 适用:大量 I/O 等待(数据库查询、HTTP 调用)性能考量
| Worker 类型 | 并发能力 | 内存开销 | 上下文切换 | 适用场景 |
|---|---|---|---|---|
| sync | 1 req/worker | 最低 | 进程级 | CPU 密集型、简单 API |
| gthread | threads/worker | 低 | 线程级 | 中等 I/O、通用场景 |
| gevent | 数百 req/worker | 中 | 协程级 | 高 I/O、长连接 |
| eventlet | 数百 req/worker | 中 | 协程级 | 高 I/O、WebSocket |
设计动机
| 设计 | 解决的问题 |
|---|---|
| 多 Worker 进程 | 利用多核 CPU,一个崩溃不影响其他 |
| Master-Worker 架构 | Master 负责管理,Worker 专注请求处理 |
| 协程 Worker | 解决同步 Worker 在 I/O 等待时浪费 CPU 的问题 |
2 * cores + 1 公式 | 平衡 CPU 利用率与上下文切换开销 |
6.3 Docker 多阶段构建原理
多阶段构建将构建环境和运行环境分离,显著减小最终镜像体积。
构建流程
+---------------------------------------------------------------+
| Docker 多阶段构建 |
+---------------------------------------------------------------+
| |
| Stage 1: builder (构建阶段) |
| ┌──────────────────────────────────────────────────────┐ |
| │ FROM python:3.11 as builder │ |
| │ # 包含编译工具链、头文件等 │ |
| │ COPY requirements.txt . │ |
| │ RUN pip install --no-cache-dir -r requirements.txt │ |
| │ COPY . . │ |
| │ # 可能包含:编译 C 扩展、下载依赖、运行测试 │ |
| └──────────────────────┬───────────────────────────────┘ |
| │ 仅复制产物 |
| ▼ |
| Stage 2: production (运行阶段) |
| ┌──────────────────────────────────────────────────────┐ |
| │ FROM python:3.11-slim as production │ |
| │ # 精简基础镜像,无编译工具 │ |
| │ COPY --from=builder /usr/local/lib/python3.11/ \ │ |
| │ /usr/local/lib/python3.11/ │ |
| │ COPY --from=builder /app /app │ |
| │ WORKDIR /app │ |
| │ CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", │ |
| │ "app:app"] │ |
| └──────────────────────────────────────────────────────┘ |
| |
| 最终镜像只包含 Stage 2 的内容 |
| Stage 1 的所有中间层都不会出现在最终镜像中 |
+---------------------------------------------------------------+dockerfile
# 完整多阶段构建示例
FROM python:3.11 as builder
WORKDIR /build
COPY requirements.txt .
# 安装到指定目录,方便后续复制
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt
# 可选:运行测试
COPY . .
RUN python -m pytest tests/ -q
# 最终镜像
FROM python:3.11-slim
WORKDIR /app
# 只复制已安装的依赖和源代码
COPY --from=builder /install /usr/local
COPY --from=builder /build /app
EXPOSE 8000
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]性能考量
| 策略 | 镜像大小 | 构建时间 | 安全性 |
|---|---|---|---|
| 单阶段(完整) | 大(~900MB) | 短 | 差(含工具链和缓存) |
| 多阶段(slim) | 中(~200MB) | 中 | 好(无编译工具) |
| 多阶段(alpine) | 小(~50MB) | 长 | 最好(最小攻击面) |
设计动机
| 设计 | 解决的问题 |
|---|---|
| 多阶段分离 | 构建依赖(gcc、make)不应出现在生产镜像中 |
| slim/alpine 基础镜像 | 减小体积 = 更快部署 + 更少漏洞面 |
--no-cache-dir | 不缓存 pip 下载,减小中间层 |
| 精确 COPY | 只复制产物,不复制 .git、tests、docs |
6.4 知识关联
+---------------------+ +------------------------+ +--------------------+
| pytest Fixture | | Gunicorn Worker | | Docker 多阶段 |
| 作用域链 | | 模型 | | 构建 |
+---------------------+ +------------------------+ +--------------------+
| session: 全局共享 | | sync: CPU 密集型 | | Builder: 完整工具 |
| module: 文件级 |--------->| gthread: 混合型 |--------->| Production: 精简 |
| function: 测试级 | | gevent: I/O 密集型 | | COPY --from |
| 自动 teardown | | Master-Worker | | 安全隔离 |
+---------------------+ +------------------------+ +--------------------+
| | |
v v v
+---------------------------------------------------------------------------+
| 设计目标 |
| 测试隔离 · 执行效率 · 并发能力 · 资源利用 · 安全部署 · 可维护性 |
+---------------------------------------------------------------------------+总结
| 知识点 | 说明 |
|---|---|
| test_client | Flask 测试客户端 |
| pytest | 测试框架 |
| fixtures | 测试准备 |
| Gunicorn | WSGI 服务器 |
| Docker | 容器化部署 |
| Nginx | 反向代理 |