Skip to content

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:app

3.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

性能考量

作用域创建次数隔离性性能适用场景
session1 次最好配置加载、数据库连接池
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 类型并发能力内存开销上下文切换适用场景
sync1 req/worker最低进程级CPU 密集型、简单 API
gthreadthreads/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_clientFlask 测试客户端
pytest测试框架
fixtures测试准备
GunicornWSGI 服务器
Docker容器化部署
Nginx反向代理