如何用Pytest测试Flask框架

1,301 阅读7分钟

本教程包括:

  1. 在Flask应用中设置Pytest
  2. 编写Pytest测试
  3. 分组测试

在任何编程语言中编写测试都是困难的,但这并不意味着它是困难的。在本教程中,我将向您展示如何使用Flask和Pytest轻松编写和运行测试。

作为奖励,我们还将集成一个CI/CD管道,使用CircleCI在Flask应用上运行测试。

前提条件

要从本教程中获得最大的收获,你需要:

  • Python编程语言的基本了解
  • 了解Flask框架
  • 对测试的基本了解

你还需要有这些安装和设置。

  1. Python--在你的机器上安装了>=3.5的版本
  2. 一个GitHub账户;你可以在这里创建一个
  3. 一个CircleCI账户;你可以在这里创建一个
  4. 来自GitHub的项目库;在这里克隆它

一旦项目被克隆,你还需要使用命令pip install -r requirements.txt ,从项目的根文件夹中安装依赖项。

在我们深入学习教程之前,了解一下Flask是什么以及它是如何工作的可能会有所帮助。

为什么使用Flask?

Flask是一个用Python编写的轻量级微型网络框架。Flask只预装了核心功能,这样你就可以根据你的项目要求挑选定制的功能。Flask不会决定使用哪个数据库或默认的分析器是什么。所有这些都可以通过修改来改变,这使得它可以扩展和灵活地满足你的开发团队的新兴需求。

在这个项目教程中,我们将使用一个用Flask构建的简单的book-retrieval API。我们将使用该API来展示我们如何在Flask应用程序中编写测试。为了保持本教程的简单性,我们将专注于测试API而不是构建它。API应用程序可以在克隆仓库的项目根目录下的api.py 文件中找到。

现在我们知道了什么是Flask以及如何使用它,我们可以在这些知识的基础上学习Pytest ,以及如何在Flask应用上设置它。

什么是Pytest,如何使用它?

Pytest是一个Python测试框架,旨在协助开发人员编写更好的系统,并自信地发布它们。使用Pytest编写小型和可扩展的测试非常简单。这个代码片断显示了Pytest测试的基本布局。

from api import app # Flask instance of the API

def test_index_route():
    response = app.test_client().get('/')

    assert response.status_code == 200
    assert response.data.decode('utf-8') == 'Testing, Flask!'

测试Flask需要我们首先从我们的api (在我们的应用程序中创建)导入一个Flask实例app ,如前面的片段所示。然后,导入的实例从Flask中暴露出一个test_client() 方法,该方法包含向被测应用程序发出HTTP 请求所需的功能。在这种情况下,它是对我们默认的(/)API端点的请求。然后我们断言,在对字节对象进行解码后,test_client() 所收到的响应是我们预期的。在下面的步骤中,我们将使用这种格式来编写我们的测试。

注意默认情况下,Pytest使用utf-8 编解码器对API响应进行编码。我们需要使用decode() 方法将我们从测试客户端收到的byte 对象转换为可读的字符串响应,如前面的代码片断中所示。

在Flask上设置Pytest

安装Pytest很简单。如果你克隆了资源库,它已经安装好了,你可以跳过这一步。如果你没有克隆仓库,在终端上运行这个命令。

pip install pytest

注意如果你使用的是虚拟环境,在进行安装之前先激活它。使用虚拟环境来隔离不同的应用程序和它们相关的 Python 包是最好的做法。

导入 Pytest 模块

安装完成后,导入Pytest模块。这个片段可以放在任何测试文件上。

import pytest

测试的命名规则

在本教程中,将测试放在应用程序根目录下的tests 目录中。Pytest建议测试文件名以格式test_*.py 开始,或以格式**_test.py 结束,使用这些格式可以使Pytest轻松地自动发现测试文件,也可以在运行测试时尽量减少混乱。在创建测试时,我们也会在test methods 之前加上testtest_index_route():

用Pytest编写测试

现在我们知道了如何在Flask应用中设置Pytest,我们可以开始为我们的图书检索API编写测试。我们的第一个测试将是针对/bookapi/books 路由。

# get books data

books = [
    {
        "id": 1,
        "title": "CS50",
        "description": "Intro to CS and art of programming!",
        "author": "Havard",
        "borrowed": False
    },
    {
        "id": 2,
        "title": "Python 101",
        "description": "little python code book.",
        "author": "Will",
        "borrowed": False
    }
]

@app.route("/bookapi/books")
def get_books():
    """ function to get all books """
    return jsonify({"Books": books})

对于这个路由,我们所做的就是返回一个我们在books 变量中硬编码的书籍列表。我们将在下一步测试这个端点。使用相同的格式,我们将定义我们的测试函数,并断言test_client() 所收到的响应是我们所期望的。

import json
from api import app

def test_get_all_books():
    response = app.test_client().get('/bookapi/books')
    res = json.loads(response.data.decode('utf-8')).get("Books")
    assert type(res[0]) is dict
    assert type(res[1]) is dict
    assert res[0]['author'] == 'Havard'
    assert res[1]['author'] == 'Will'
    assert response.status_code == 200
    assert type(res) is list
    ....

这个测试片段是用来确保我们能收到一个图书列表。我们通过断言test_client() 所收到的响应确实是我们所期望的书籍列表来做到这一点。list 我们还断言,该响应包含dictionaries ,即我们定义的书籍对象中的各个书籍。最后,我们还断言,列表中第一本书的作者是Havard ,第二本书的作者是Will 。这就是我们写一个测试来从我们的/bookapi/books 端点获取所有的书所需要的全部内容。

我们需要确保测试通过,要做到这一点,我们首先需要知道如何在Pytest中运行测试。

用Pytest运行测试

要运行Pytest测试,您可以在终端中使用py.testpytest 。您也可以通过在Pytest命令后明确指定文件名来运行单个文件。pytest test_api.py.当这些命令被执行时,Pytest会自动在根目录或指定的单个文件中找到所有测试。

当没有明确定义测试文件时,Pytest会执行根目录中遵循标准命名模式的任何测试,而不需要你指定文件名或目录。

Executing our first test

我们已经验证了我们的测试被成功执行了!Pytest用绿色的点标记通过的测试. ;用红色的标记失败的测试F 。计算点或F的数量来计算有多少测试通过和失败,以及执行的顺序。

注意。 如果你在向控制台调试测试,并且需要打印出一个响应,你可以使用$ pytest -s test_*.py ,记录到stdout。当定义了Pytests 选项时,你可以在测试内部进行控制台信息,并在测试执行过程中对输出进行调试。

现在我们已经成功地执行了我们的第一个测试,我们可以整合CircleCI,当我们推送到main 分支时,自动运行我们的测试。

设置CircleCI

在你的根目录下创建一个.circleci 目录,然后在那里添加一个config.yml 文件。该配置文件包含每个项目的CircleCI配置。我们将使用CircleCI Pythonorb 来执行我们的测试,作为这个配置的一部分。

version: 2.1
orbs:
  python: circleci/python@2.0.3
jobs:
  build-and-test:
    docker:
      - image: cimg/python:3.10.4
    steps:
      - checkout
      - python/install-packages:
          pkg-manager: pip
      - run:
          name: Run tests
          command: pytest
workflows:
  sample:
    jobs:
      - build-and-test

CircleCI orbs是YAML代码的可重复使用的包。Orbs将多行代码浓缩为一行。在这个例子中,这行代码是:python: circleci/python@2.0.3 。你可能需要启用组织设置,以允许在CircleCI仪表板中使用第三方bs,或者向你的组织的CircleCI管理员申请许可。

设置好配置后,提交你的修改,然后将你的修改推送到GitHub。

登录CircleCI,导航到项目仪表板。与您的GitHub用户名或组织相关的GitHub仓库,以及我们要在CircleCI中设置的特定仓库。在我们的例子中,它是testing-flask-with-pytest

Select project

接下来,点击Set Up Project,开始在CircleCI上建立我们的项目。这将显示一个弹出的模式,默认选项是使用项目库内的配置文件。输入放置配置文件的分支的名称。

Select configuration

点击Set Up Project来完成这一过程。

好了在CircleCI仪表板上,点击构建来查看细节。您可以验证运行第一个Pytest测试并将其集成到CircleCI是成功的。

Pipeline Setup Success

现在,您已经成功地设置了持续集成,下一步是对测试进行分组,并运行分组测试的批次。

分组和运行成批的测试

随着应用功能的增加,你需要增加测试的数量,以确保一切正常。很容易被大量的测试脚本所淹没。不过您不必担心,Pytest已经解决了这个问题。Pytest允许你通过分组测试从一个文件中运行多个测试。

Pytest提供了markers ,您可以用它来设置测试功能的属性和特征。

使用Pytest的测试标记

我们可以使用标记来赋予测试不同的行为,如跳过测试,运行测试的子集,甚至是失败。Pytest捆绑的一些默认标记包括xfail,skip, 和parameterize 。对于我们的项目,我们将创建一个自定义标记,将所有对我们的/bookapi/books/bookapi/book/:id 端点执行GET 请求的测试分组。

这里是一个你将用于创建自定义标记的结构的例子。

@pytest.mark.<markername>
def test_method():
  # test code

要在Pytest中使用自定义标记,请将其定义为pytest 命令中的一个参数。

$ pytest -m <markername>

-m <markername> 是我们将用于测试的自定义标记名称。

需要注意的是,你需要在你的测试文件中导入pytest ,以使用Pytest的标记。

标记也需要注册,以便Pytest可以抑制对它们的警告。将此添加到您的pytest.ini 文件中。

[pytest]
markers =
    <markername>: Marker description

使用标记器对测试进行分组

在本教程中,我们希望将向我们的/bookapi 端点发出GET 请求的测试分组。

创建一个名为get_request 的客户标记,并像这样将其添加到测试中。

import pytest
...
# Other imports here

@pytest.mark.get_request
def test_get_book_by_id():
    response = app.test_client().get('/bookapi/books/1')
    res = json.loads(response.data.decode('utf-8')).get("Book")
    print(res)
    assert res['id'] == 1
    assert res['author'] == 'Havard'
    assert res['title'] == 'CS50'
    assert response.status_code == 200

运行带有pytest -m get_request 参数的测试,会执行测试文件中所有带有@pytest.mark.get_request 装饰符的测试。你也可以通过在终端运行它来验证这一点。

现在,提交并推送你的修改到GitHub,并验证管道是否成功执行。

Passing Tests

很好!我们所有的测试都成功执行了。我们所有的测试都成功执行了。

总结

在本教程中,你已经用Pytest设置了测试,执行了它们,学会了用Pytest标记对测试进行分组。您学会了如何使用Pytest命令行参数来执行测试,以及如何使用test_client() 方法来进行HTTP 请求,您还知道如何在测试中使用收到的响应。