云计算百科
云计算领域专业知识百科平台

Python web编程教程(二十一)

第 11 章:API 测试与文档

本章摘要:本章围绕 Flask 博客系统的 API 测试与文档展开。首先介绍使用 Postman 进行可视化接口测试,包括基本请求、测试集与环境变量的使用;随后讲解 VS Code REST Client 扩展的轻量替代方案。接着重点演示如何用 pytest 编写自动化测试,覆盖用户列表、创建、查询与删除等核心接口,并给出完整测试代码与运行输出。之后引入 Flasgger(Swagger)为 API 自动生成交互式文档,展示集成步骤与路由文档写法。最后总结自动化测试的最佳实践,强调测试先行(TDD)对保障代码质量的重要性。

11.1 用 Postman 测试 API

Postman 是最流行的 API 测试工具。你可以可视化地发送 HTTP 请求、查看响应、管理测试集。
基本使用:

  • 打开 Postman,点击 “New” → “HTTP Request”
  • 选择方法(GET/POST/PUT/DELETE)
  • 输入 URL:http://127.0.0.1:5000/api/v1/users
  • 对于 POST/PUT 请求:
    • 选择 “Body” 标签
    • 选择 “raw” → “JSON”
    • 输入 JSON 数据
  • 点击 “Send” 发送请求
  • 下方显示响应状态码、响应头、响应体
  • 创建测试集(Collection):

    把相关的请求组织在一起,方便反复运行:

  • 点击 “New” → “Collection”
  • 命名为 “Flask Blog API”
  • 在 Collection 下添加各个请求
  • 可以设置环境变量(如 {{base_url}})避免重复写 URL
  • 环境变量:

    Environment: Development
    base_url = http://127.0.0.1:5000
    api_version = api/v1

    请求 URL 写成 {{base_url}}/{{api_version}}/users,切换环境时只需改变量值。

    11.2 使用 VS Code REST Client

    如果不想安装 Postman,VS Code 的 REST Client 扩展同样好用。创建 .http 文件直接发请求:

    ### 获取用户列表
    GET http://127.0.0.1:5000/api/v1/users?page=1&per_page=5

    ### 创建用户
    POST http://127.0.0.1:5000/api/v1/users
    Content-Type: application/json

    {
    "username": "newuser",
    "email": "new@example.com",
    "role": "user"
    }

    ### 获取单个用户
    GET http://127.0.0.1:5000/api/v1/users/1

    ### 更新用户
    PATCH http://127.0.0.1:5000/api/v1/users/1
    Content-Type: application/json

    {
    "role": "editor"
    }

    ### 删除用户
    DELETE http://127.0.0.1:5000/api/v1/users/2

    ### 创建文章
    POST http://127.0.0.1:5000/api/v1/posts
    Content-Type: application/json

    {
    "title": "我的第一篇文章",
    "content": "这是内容…",
    "user_id": 1
    }

    每个 ### 分隔一个请求,点击代码上方的 “Send Request” 即可发送。

    11.3 用 Python 写自动化测试

    手动测试效率低且容易遗漏。自动化测试能保证代码修改后功能不退化。
    安装 pytest:

    pip install pytest

    编写测试:

    # tests/test_user_api.py
    import pytest
    import json
    from app import create_app, db
    from app.models import User

    @pytest.fixture
    def app():
    """每个测试使用独立的内存数据库"""
    app = create_app('testing')
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' # 内存数据库
    with app.app_context():
    db.create_all()
    # 添加测试数据
    user1 = User(username='user1', email='user1@test.com')
    user2 = User(username='user2', email='user2@test.com')
    db.session.add_all([user1, user2])
    db.session.commit()

    yield app

    with app.app_context():
    db.drop_all()

    @pytest.fixture
    def client(app):
    """测试客户端"""
    return app.test_client()

    class TestGetUsers:
    """测试获取用户列表"""

    def test_get_all_users(self, client):
    resp = client.get('/api/v1/users')
    assert resp.status_code == 200
    data = resp.get_json()
    assert data['code'] == 200
    assert len(data['data']) == 2

    def test_pagination(self, client):
    resp = client.get('/api/v1/users?per_page=1')
    data = resp.get_json()
    assert len(data['data']) == 1
    assert data['pagination']['total'] == 2
    assert data['pagination']['pages'] == 2

    def test_search(self, client):
    resp = client.get('/api/v1/users?search=user1')
    data = resp.get_json()
    assert len(data['data']) == 1
    assert data['data'][0]['username'] == 'user1'

    class TestCreateUser:
    """测试创建用户"""

    def test_create_success(self, client):
    resp = client.post('/api/v1/users', json={
    'username': 'newuser',
    'email': 'new@test.com'
    })
    assert resp.status_code == 201
    data = resp.get_json()
    assert data['data']['username'] == 'newuser'

    def test_create_missing_username(self, client):
    resp = client.post('/api/v1/users', json={
    'email': 'test@test.com'
    })
    assert resp.status_code == 400

    def test_create_duplicate(self, client):
    resp = client.post('/api/v1/users', json={
    'username': 'user1',
    'email': 'different@test.com'
    })
    assert resp.status_code == 409

    class TestGetSingleUser:
    """测试获取单个用户"""

    def test_get_existing(self, client):
    resp = client.get('/api/v1/users/1')
    assert resp.status_code == 200
    data = resp.get_json()
    assert data['data']['id'] == 1

    def test_get_not_found(self, client):
    resp = client.get('/api/v1/users/999')
    assert resp.status_code == 404

    class TestDeleteUser:
    """测试删除用户"""

    def test_delete_success(self, client):
    resp = client.delete('/api/v1/users/1')
    assert resp.status_code == 200

    def test_delete_not_found(self, client):
    resp = client.delete('/api/v1/users/999')
    assert resp.status_code == 404

    运行测试:

    pytest tests/ -v

    输出:

    tests/test_user_api.py::TestGetUsers::test_get_all_users PASSED
    tests/test_user_api.py::TestGetUsers::test_pagination PASSED
    tests/test_user_api.py::TestGetUsers::test_search PASSED
    tests/test_user_api.py::TestCreateUser::test_create_success PASSED
    tests/test_user_api.py::TestCreateUser::test_create_missing_username PASSED
    tests/test_user_api.py::TestCreateUser::test_create_duplicate PASSED
    tests/test_user_api.py::TestGetSingleUser::test_get_existing PASSED
    tests/test_user_api.py::TestGetSingleUser::test_get_not_found PASSED
    tests/test_user_api.py::TestDeleteUser::test_delete_success PASSED
    tests/test_user_api.py::TestDeleteUser::test_delete_not_found PASSED

    10 passed in 0.45s

    11.4 API 文档:Flasgger (Swagger)

    API 文档让使用者知道有哪些接口、怎么调用。Swagger/OpenAPI 是 API 文档的标准。

    安装 Flasgger:

    pip install flasgger

    集成到 Flask:

    from flasgger import Swagger

    def create_app(config_name='default'):
    app = Flask(__name__)
    # … 其他配置 …

    swagger_config = {
    'headers': [],
    'specs': [{
    'endpoint': 'apispec',
    'route': '/apispec.json',
    'rule_filter': lambda rule: True,
    'model_filter': lambda tag: True,
    }],
    'static_url_path': '/flasgger_static',
    'swagger_ui': True,
    'specs_route': '/docs/'
    }

    swagger_template = {
    'info': {
    'title': 'Flask Blog API',
    'description': '一个博客系统的 RESTful API',
    'version': '1.0.0',
    }
    }

    Swagger(app, config=swagger_config, template=swagger_template)

    # … 其他代码 …
    return app

    在路由中添加文档:

    @user_bp.route('/users', methods=['GET'])
    def get_users():
    """
    获取用户列表

    tags:
    – 用户
    parameters:
    – name: page
    in: query
    type: integer
    default: 1
    description: 页码
    – name: per_page
    in: query
    type: integer
    default: 10
    description: 每页条数
    – name: search
    in: query
    type: string
    description: 搜索关键词
    responses:
    200:
    description: 成功获取用户列表
    schema:
    type: object
    properties:
    code:
    type: integer
    example: 200
    data:
    type: array
    items:
    type: object
    properties:
    id:
    type: integer
    username:
    type: string
    """

    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 10, type=int)
    search = request.args.get('search', '').strip()

    query = User.query
    if search:
    query = query.filter(
    User.username.contains(search) | User.email.contains(search)
    )

    pagination = query.paginate(page=page, per_page=per_page, error_out=False)

    return api_response(
    data=[user.to_dict() for user in pagination.items],
    pagination={
    'page': pagination.page,
    'per_page': pagination.per_page,
    'total': pagination.total,
    'pages': pagination.pages,
    }
    )

    运行后访问 http://127.0.0.1:5000/docs/,你会看到自动生成的 API 文档页面,包含:

    • 所有接口列表
    • 每个接口的参数说明
    • 在线测试功能(直接在文档页面发请求)

    11.5 自动化测试的最佳实践

    # tests/conftest.py —— 公共测试夹具
    import pytest
    from app import create_app, db

    @pytest.fixture
    def app():
    """创建测试应用"""
    app = create_app('testing')
    app.config['TESTING'] = True
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' # 内存数据库,测试完自动消失

    with app.app_context():
    db.create_all()
    # 初始化测试数据
    from app.models import User, Post
    user = User(username='admin', email='admin@test.com', role='admin')
    db.session.add(user)
    db.session.commit()

    post = Post(title='测试文章', content='测试内容', user_id=1)
    db.session.add(post)
    db.session.commit()

    yield app

    with app.app_context():
    db.session.remove()
    db.drop_all()

    @pytest.fixture
    def client(app):
    """测试客户端"""
    return app.test_client()

    @pytest.fixture
    def runner(app):
    """CLI 测试运行器"""
    return app.test_cli_runner()

    # 运行所有测试
    pytest

    # 运行特定文件
    pytest tests/test_user_api.py

    # 运行特定测试类
    pytest tests/test_user_api.py::TestCreateUser

    # 运行特定测试方法
    pytest tests/test_user_api.py::TestCreateUser::test_create_success

    # 显示详细输出
    pytest -v

    # 显示打印输出
    pytest -s

    # 生成覆盖率报告
    pip install pytest-cov
    pytest –cov=app tests/

    💡 提示
    测试不是可选的“额外工作”——它是专业开发的标配。好的测试能让你放心地重构代码、添加功能,而不担心“改了这里会不会弄坏那里”。测试先行(TDD)是一种很好的习惯:先写测试描述预期行为,再写代码让它通过。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Python web编程教程(二十一)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!