17、Flask 应用的 RESTful API 与测试实践

Flask 应用的 RESTful API 与测试实践

1. 异常处理

在应用开发中,异常处理是确保系统稳定性和用户体验的重要环节。为了避免在视图函数中重复添加异常捕获代码,可以安装全局异常处理程序。

首先定义了 ValidationError 异常类:

class ValidationError(ValueError):
    pass

然后为该异常创建了全局处理程序:

@api.errorhandler(ValidationError)
def validation_error(e):
    return bad_request(e.args[0])

这里的 errorhandler 装饰器用于注册异常处理程序,它接收一个异常类作为参数。当抛出指定类的异常时,装饰的函数将被调用。

2. 资源端点实现

接下来是实现处理不同资源的路由。GET 请求通常是最简单的,因为它们只返回信息而不需要进行任何更改。以下是博客文章的两个 GET 处理程序示例:

@api.route('/posts/')
@auth.login_required
def get_posts():
    posts = Post.query.all()
    return jsonify({ 'posts': [post.to_json() for post in posts] })

@api.route('/posts/<int:id>')
@auth.login_required
def get_post(id):
    post = Post.query.get_or_404(id)
    return jsonify(post.to_json())

第一个路由处理文章集合的请求,使用列表推导式生成所有文章的 JSON 版本。第二个路由返回单篇博客文章,如果给定的 ID 在数据库中不存在,则返回 404 错误。

POST 处理程序用于在数据库中插入新的博客文章:

@api.route('/posts/', methods=['POST'])
@permission_required(Permission.WRITE_ARTICLES)
def new_post():
    post = Post.from_json(request.json)
    post.author = g.current_user
    db.session.add(post)
    db.session.commit()
    return jsonify(post.to_json()), 201, \
           {'Location': url_for('api.get_post', id=post.id, _external=True)}

这个视图函数使用 permission_required 装饰器确保认证用户有写博客文章的权限。创建博客文章后,返回 201 状态码,并在响应头中添加新创建资源的 URL。

PUT 处理程序用于编辑现有资源:

@api.route('/posts/<int:id>', methods=['PUT'])
@permission_required(Permission.WRITE_ARTICLES)
def edit_post(id):
    post = Post.query.get_or_404(id)
    if g.current_user != post.author and \
            not g.current_user.can(Permission.ADMINISTER):
        return forbidden('Insufficient permissions')
    post.body = request.json.get('body', post.body)
    db.session.add(post)
    return jsonify(post.to_json())

权限检查更为复杂,除了使用装饰器检查写博客文章的权限外,还需要确保用户是文章的作者或管理员。

以下是实现的资源列表:
| 资源 URL | 方法 | 描述 |
| — | — | — |
| /users/ | GET | 一个用户 |
| /users/ /posts/ | GET | 用户撰写的博客文章 |
| /users/ /timeline/ | GET | 用户关注的博客文章 |
| /posts/ | GET, POST | 所有博客文章 |
| /posts/ | GET, PUT | 一篇博客文章 |
| /posts/ comments/ | GET, POST | 一篇博客文章的评论 |
| /comments/ | GET | 所有评论 |
| /comments/ | GET | 一条评论 |

3. 大型资源集合的分页

对于返回资源集合的 GET 请求,当集合非常大时,处理起来会非常昂贵且难以管理。因此,可以选择对集合进行分页。以下是博客文章列表分页的实现示例:

@api.route('/posts/')
def get_posts():
    page = request.args.get('page', 1, type=int)
    pagination = Post.query.paginate(
        page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'],
        error_out=False)
    posts = pagination.items
    prev = None
    if pagination.has_prev:
        prev = url_for('api.get_posts', page=page-1, _external=True)
    next = None
    if pagination.has_next:
        next = url_for('api.get_posts', page=page+1, _external=True)
    return jsonify({
        'posts': [post.to_json() for post in posts],
        'prev': prev,
        'next': next,
        'count': pagination.total
    })

JSON 响应中的 posts 字段包含数据项, prev next 项包含上一页和下一页的资源 URL, count 值是集合中的总项数。

4. 使用 HTTPie 测试 Web 服务

为了测试 Web 服务,需要使用 HTTP 客户端。 HTTPie 是一个简洁易读的命令行客户端,可以使用 pip 安装:

(venv) $ pip install httpie

以下是一些使用 HTTPie 进行测试的示例:
- 发送 GET 请求:

(venv) $ http --json --auth <email>:<password> GET http://127.0.0.1:5000/api/v1.0/posts
  • 发送 POST 请求添加新博客文章:
(venv) $ http --auth <email>:<password> --json POST http://127.0.0.1:5000/api/v1.0/posts/ "body=I'm adding a post from the *command line*."
  • 获取认证令牌:
(venv) $ http --auth <email>:<password> --json GET http://127.0.0.1:5000/api/v1.0/token

使用返回的令牌进行 API 调用:

(venv) $ http --json --auth eyJpYXQ...: GET http://127.0.0.1:5000/api/v1.0/posts/
5. 单元测试的重要性

编写单元测试有两个重要原因:一是在实现新功能时,用于确认新代码按预期工作;二是每次修改应用程序时,执行所有单元测试以确保现有代码没有回归问题。

6. 获取代码覆盖率报告

拥有测试套件很重要,但了解其质量同样重要。代码覆盖率工具可以测量单元测试对应用程序的覆盖程度,并提供详细报告,指出哪些部分的应用程序代码未被测试。

可以使用 coverage 工具,通过 pip 安装:

(venv) $ pip install coverage

为了将覆盖率指标集成到 manage.py 启动脚本中,可以扩展自定义测试命令,添加 --coverage 可选参数:

#!/usr/bin/env python
import os
COV = None
if os.environ.get('FLASK_COVERAGE'):
    import coverage
    COV = coverage.coverage(branch=True, include='app/*')
    COV.start()
# ...
@manager.command
def test(coverage=False):
    """Run the unit tests."""
    if coverage and not os.environ.get('FLASK_COVERAGE'):
        import sys
        os.environ['FLASK_COVERAGE'] = '1'
        os.execvp(sys.executable, [sys.executable] + sys.argv)
    import unittest
    tests = unittest.TestLoader().discover('tests')
    unittest.TextTestRunner(verbosity=2).run(tests)
    if COV:
        COV.stop()
        COV.save()
        print('Coverage Summary:')
        COV.report()
        basedir = os.path.abspath(os.path.dirname(__file__))
        covdir = os.path.join(basedir, 'tmp/coverage')
        COV.html_report(directory=covdir)
        print('HTML version: file://%s/index.html' % covdir)
        COV.erase()
# ...

运行测试并生成覆盖率报告:

(venv) $ python manage.py test --coverage

示例报告显示整体覆盖率为 44%,模型类的覆盖率为 72%,而主蓝图和认证蓝图中的 views.py 文件以及 api_1_0 蓝图中的路由覆盖率很低。

7. Flask 测试客户端

部分应用程序代码依赖于运行中的应用程序创建的环境,例如视图函数需要访问 Flask 上下文全局变量、处理表单数据或需要登录用户。Flask 提供了测试客户端来解决这个问题。

以下是使用测试客户端的单元测试框架示例:

import unittest
from app import create_app, db
from app.models import User, Role

class FlaskClientTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.create_all()
        Role.insert_roles()
        self.client = self.app.test_client(use_cookies=True)

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()

    def test_home_page(self):
        response = self.client.get(url_for('main.index'))
        self.assertTrue('Stranger' in response.get_data(as_text=True))

测试客户端可以模拟浏览器发送请求, use_cookies=True 选项允许处理依赖于 cookie 的功能。

为了避免在测试中处理 CSRF 令牌的麻烦,可以在测试配置中禁用 CSRF 保护:

class TestingConfig(Config):
    #...
    WTF_CSRF_ENABLED = False

以下是一个更高级的单元测试示例,模拟新用户注册、登录、确认账户和注销的流程:

class FlaskClientTestCase(unittest.TestCase):
    # ...
    def test_register_and_login(self):
        # register a new account
        response = self.client.post(url_for('auth.register'), data={
            'email': 'john@example.com',
            'username': 'john',
            'password': 'cat',
            'password2': 'cat'
        })
        self.assertTrue(response.status_code == 302)

        # login with the new account
        response = self.client.post(url_for('auth.login'), data={
            'email': 'john@example.com',
            'password': 'cat'
        }, follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertTrue(re.search('Hello,\s+john!', data))
        self.assertTrue('You have not confirmed your account yet' in data)

        # send a confirmation token
        user = User.query.filter_by(email='john@example.com').first()
        token = user.generate_confirmation_token()
        response = self.client.get(url_for('auth.confirm', token=token),
                                   follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertTrue('You have confirmed your account' in data)

        # log out
        response = self.client.get(url_for('auth.logout'),
                                   follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertTrue('You have been logged out' in data)

这个流程图展示了新用户注册、登录、确认账户和注销的流程:

graph LR
    A[开始] --> B[注册账户]
    B --> C{注册成功?}
    C -- 是 --> D[登录账户]
    C -- 否 --> B
    D --> E{登录成功?}
    E -- 是 --> F[确认账户]
    E -- 否 --> D
    F --> G{确认成功?}
    G -- 是 --> H[注销账户]
    G -- 否 --> F
    H --> I[结束]

通过以上步骤和示例,我们可以看到如何构建一个完整的 Flask 应用,包括异常处理、资源端点实现、分页、测试等方面。这些技术可以帮助我们开发出更加健壮、可维护的 Web 应用。

Flask 应用的 RESTful API 与测试实践

8. 视图函数测试要点

在使用 Flask 测试客户端测试视图函数时,需要注意以下几个要点:
- 上下文环境 :视图函数依赖于 Flask 的上下文环境,如 request session 等。测试客户端模拟了应用运行时的环境,确保视图函数能正常执行。
- 请求方法 :不同的请求方法(如 GET、POST、PUT 等)需要使用相应的测试客户端方法进行模拟。例如,使用 client.get() 模拟 GET 请求, client.post() 模拟 POST 请求。
- 表单数据 :对于 POST 请求,需要提供正确的表单数据。注意在测试配置中禁用 CSRF 保护,避免处理 CSRF 令牌的麻烦。
- 重定向处理 :使用 follow_redirects=True 参数可以让测试客户端像浏览器一样自动处理重定向,方便验证重定向后的页面内容。

9. 不同类型请求测试示例

以下是对不同类型请求进行测试的详细示例:

GET 请求测试

def test_get_posts():
    response = client.get(url_for('api.get_posts'))
    assert response.status_code == 200
    data = response.get_json()
    assert 'posts' in data

此测试用例验证了获取博客文章列表的 GET 请求,检查响应状态码是否为 200,并确认响应数据中包含 posts 字段。

POST 请求测试

def test_new_post():
    post_data = {
        'body': 'This is a test post'
    }
    response = client.post(url_for('api.new_post'), json=post_data)
    assert response.status_code == 201
    data = response.get_json()
    assert 'body' in data
    assert data['body'] == 'This is a test post'

该测试用例模拟创建新博客文章的 POST 请求,检查响应状态码是否为 201,并验证响应数据中的文章内容是否与发送的数据一致。

PUT 请求测试

def test_edit_post():
    post = Post.query.first()
    if post:
        post_data = {
            'body': 'Updated post body'
        }
        response = client.put(url_for('api.edit_post', id=post.id), json=post_data)
        assert response.status_code == 200
        data = response.get_json()
        assert data['body'] == 'Updated post body'

此测试用例针对编辑现有博客文章的 PUT 请求,首先获取一篇文章,然后发送更新数据,检查响应状态码是否为 200,并验证更新后的文章内容是否正确。

10. 测试总结与建议

在进行 Flask 应用的测试时,我们可以总结出以下几点建议:
- 全面覆盖 :尽量对应用的各个功能模块进行测试,包括视图函数、表单处理、数据库操作等,提高代码覆盖率。
- 边界条件测试 :除了正常情况的测试,还需要考虑边界条件,如输入为空、输入超出范围等,确保应用在各种情况下都能正常工作。
- 独立测试 :每个测试用例应该相互独立,避免测试用例之间的依赖关系,确保测试结果的准确性。
- 持续集成 :将测试集成到开发流程中,每次代码变更后自动运行测试,及时发现和解决问题。

11. 测试与生产环境的差异

在测试和生产环境中,应用的运行可能会存在一些差异,需要特别注意:
| 环境 | 特点 | 注意事项 |
| — | — | — |
| 测试环境 | 通常使用测试数据库,数据可以随时重置;禁用 CSRF 保护,方便测试表单提交;可以模拟各种异常情况。 | 确保测试数据的多样性和代表性,覆盖各种可能的情况。 |
| 生产环境 | 使用真实的数据库,数据需要谨慎处理;启用 CSRF 保护,保障应用安全;需要考虑性能和稳定性。 | 定期备份数据,监控应用性能,及时处理生产环境中的问题。 |

12. 未来优化方向

为了进一步提高 Flask 应用的质量和性能,可以考虑以下优化方向:
- 自动化测试框架 :引入更强大的自动化测试框架,如 pytest,提高测试效率和可维护性。
- 性能测试 :使用性能测试工具,如 Locust,对应用进行性能测试,找出性能瓶颈并进行优化。
- 安全测试 :进行安全测试,如 SQL 注入、XSS 攻击等,确保应用的安全性。

这个流程图展示了从开发到测试再到优化的整个流程:

graph LR
    A[开发] --> B[单元测试]
    B --> C{测试通过?}
    C -- 是 --> D[集成测试]
    C -- 否 --> A
    D --> E{集成测试通过?}
    E -- 是 --> F[性能测试]
    E -- 否 --> A
    F --> G{性能达标?}
    G -- 是 --> H[安全测试]
    G -- 否 --> A
    H --> I{安全测试通过?}
    I -- 是 --> J[部署上线]
    I -- 否 --> A

通过以上对 Flask 应用的 RESTful API 开发和测试的详细介绍,我们可以看到,从异常处理到资源端点实现,再到测试和优化,每个环节都至关重要。遵循这些最佳实践和技术,能够帮助我们开发出更加健壮、高效、安全的 Web 应用。在未来的开发过程中,我们可以不断探索和应用新的技术和方法,进一步提升应用的质量和性能。

【3D应力敏感度分析拓扑优化】【基于p-范数全局应力衡量的3D敏感度分析】基于伴随方法的有限元分析和p-范数应力敏感度分析(Matlab代码实现)内容概要:本文档介绍了基于伴随方法的有限元分析p-范数全局应力衡量的3D应力敏感度分析,并结合拓扑优化技术,提供了完整的Matlab代码实现方案。该方法通过有限元建模计算结构在载荷作用下的应力分布,采用p-范数对全局应力进行有效聚合,避免传统方法中应力约束过多的问题,进而利用伴随法高效求解设计变量对应力的敏感度,为结构优化提供关键梯度信息。整个流程涵盖了从有限元分析、应力评估到敏感度计算的核心环节,适用于复杂三维结构的轻量化高强度设计。; 适合人群:具备有限元分析基础、拓扑优化背景及Matlab编程能力的研究生、科研人员工程技术人员,尤其适合从事结构设计、力学仿真多学科优化的相关从业者; 使用场景及目标:①用于实现高精度三维结构的应力约束拓扑优化;②帮助理解伴随法在敏感度分析中的应用原理编程实现;③服务于科研复现、论文写作工程项目中的结构性能提升需求; 阅读建议:建议读者结合有限元理论优化算法知识,逐步调试Matlab代码,重点关注伴随方程的构建p-范数的数值处理技巧,以深入掌握方法本质并实现个性化拓展。
下载前必看:https://pan.quark.cn/s/9f13b242f4b9 Android 平板设备远程操控个人计算机的指南 Android 平板设备远程操控个人计算机的指南详细阐述了如何运用 Splashtop Remote 应用程序达成 Android 平板设备对个人计算机的远程操控。 该指南被划分为四个环节:首先,在个人计算机上获取并部署 Splashtop Remote 应用程序,并设定客户端密码;其次,在 Android 平板设备上获取并部署 Splashtop Remote 应用程序,并之建立连接至个人计算机的通道;再次,在 Splashtop Remote 应用程序中识别已部署个人计算机端软件的设备;最后,运用平板设备对个人计算机实施远程操控。 关键点1:Splashtop Remote 应用程序的部署配置* 在个人计算机上获取并部署 Splashtop Remote 应用程序,可通过官方网站或其他获取途径进行下载。 * 部署结束后,必须输入客户端密码,该密码在平板控制计算机时用作验证,密码长度至少为8个字符,且需包含字母数字。 * 在配置选项中,能够设定是否在设备启动时自动运行客户端,以及进行互联网搜索设置。 关键点2:Splashtop Remote 应用程序的 Android 版本获取部署* 在 Android 平板设备上获取并部署 Splashtop Remote 应用程序,可通过 Google Play Store 或其他获取途径进行下载。 * 部署结束后,必须输入客户端密码,该密码用于连接至个人计算机端软件。 关键点3:运用 Splashtop Remote 远程操控个人计算机* 在 Splashtop Remote 应用程序中识别...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值