Flask Rest API -第 6 部分- 测试 REST API

2025-06-07

Flask Rest API -第 6 部分- 测试 REST API

第 6 部分:测试 REST API

你好!在本系列的上一篇中,我们学习了如何
在 REST API 中执行密码重置。

在本部分中,我们将学习如何测试我们的 REST API 端点。

我们为什么要花时间编写测试?

  • 确保我们的应用程序在进行更改/重构时不会中断
  • 自动化重复的手动测试,减少人为错误
  • 能够在周五发布生产;)
  • 测试提供了更好的 CI/CD 工作流程。

希望你确信我们应该编写测试。让我们开始测试我们的 Flask 应用程序。

说到测试,有两种最流行的工具可以测试 Python 应用程序。1
unittestunittest是一个 Python标准库,这意味着它与 Python 一起分发。unittest提供了大量用于构建和运行测试的工具。

2) pytestpytest是一个 Python 库,我喜欢称它为 的超集,这意味着你可以运行unittest编写的测试。它使编写测试变得更容易、更快捷。unittestpytest

在本教程中,我们将学习如何使用编写测试,因为它使我们能够使用OOPunittest编写测试

在我们开始之前,请从app.py

app.config['MONGODB_SETTINGS'] = {
    'host': 'mongodb://localhost/movie-bag'
}

并添加

MONGODB_SETTINGS = {
    'host': 'mongodb://localhost/movie-bag'
}

对于我们来说.env,这一步是必需的,因为我们想使用不同的数据库来开发我们的应用程序和运行测试。

首先,让我们创建一个env文件来存储与测试相关的配置,我们应该将测试配置与开发和生产配置分开。

在根目录中创建一个文件.env.test并向其中添加以下配置。

touch .env.test
#~/movie-bag/.env.test

JWT_SECRET_KEY = 'super-secret'
MAIL_SERVER: "localhost"
MAIL_PORT = "1025"
MAIL_USERNAME = "support@movie-bag.com"
MAIL_PASSWORD = ""
MONGODB_SETTINGS = {
    'host': 'mongodb://localhost/movie-bag-test'
}

请注意,我们为测试配置使用了不同的数据库,这样做是因为我们希望测试和开发数据库分离。我们还希望在运行测试之前将测试数据库清空。

现在,让我们tests在根目录中创建一个新文件夹。__init__.py在该tests文件夹中创建一个新文件,同时创建一个新文件test_signup.py

mkdir tests
cd tests
touch __init__.py
touch test_signup.py
#~/movie-bag/tests/test_signup.py

import unittest
import json

from app import app
from database.db import db


class SignupTest(unittest.TestCase):

    def setUp(self):
        self.app = app.test_client()
        self.db = db.get_db()

    def test_successful_signup(self):
        # Given
        payload = json.dumps({
            "email": "paurakh011@gmail.com",
            "password": "mycoolpassword"
        })

        # When
        response = self.app.post('/api/auth/signup', headers={"Content-Type": "application/json"}, data=payload)

        # Then
        self.assertEqual(str, type(response.json['id']))
        self.assertEqual(200, response.status_code)

    def tearDown(self):
        # Delete Database collections after the test is complete
        for collection in self.db.list_collection_names():
            self.db.drop_collection(collection)

让我们一步一步地了解到底发生了什么。

首先,我们定义SignupTest扩展的类unittest.TestCaseTestCase它为我们提供有用的方法,例如setUptearDown以及断言方法。

setUp()方法每次在运行类中定义的每个方法之前都会运行SignupTestsetUp()顾名思义,它用于在运行测试之前设置测试基础架构。

在这里您可以看到我们在此方法中定义了this.appthis.db。我们使用app.test_client()而不是 ,app因为它可以更轻松地测试我们的 Flask 应用程序。此外,我们获取Database实例db.get_db()并将其设置为this.db

同样,test_successful_signup()是实际测试该功能的方法Signup。这里我们定义了一个有效载荷,它应该是一个JSON值。然后我们POST向 发送一个请求/api/auth/signup

请求的响应用于最终断言我们的Signup功能确实发送了用户 ID 和成功状态代码200

最后,每次测试方法结束后tearDown(),都会运行该方法。此方法负责清除我们的基础架构设置。这包括删除数据库集合test isolation

测试隔离

测试隔离是测试中最重要的概念之一。通常,当我们编写测试时,我们只测试一种业务逻辑。测试隔离的理念是,你的一个测试不应该以任何方式影响另一个测试。

假设你在一个测试中创建了一个用户,而在另一个测试中测试登录。为了遵循测试隔离,你不能依赖于用户创建测试中创建的用户,而应该在你将要测试登录的测试中创建用户。为什么?因为你的登录测试可能在用户创建测试之前运行,这会导致你的测试失败。

另外,如果我们不删除上次测试中创建的用户,并尝试再次运行测试,测试将会失败,因为用户已经存在。
因此,我们应该始终从空状态测试功能,最简单的方法就是删除数据库中的所有集合。

在运行我们的第一个测试之前,请确保将ENV_FILE_LOCATION带有位置的环境变量导出到测试环境文件。

要设置此值 mac/linux 可以运行以下命令:

export ENV_FILE_LOCATION=./.env.test

Windows 用户可以运行以下命令:

set ENV_FILE_LOCATION=./.env.test

确保您已经使用 激活了您的虚拟环境pipenv shell

要运行测试,请在终端中输入此命令。

python -m unittest tests/test_signup.py

您应该能够看到如下输出:

.
----------------------------------------------------------------------
Ran 1 test in 1.023s

OK

这意味着我们的测试运行成功。

如果您遇到任何错误,请随时发表评论,我随时准备为您提供帮助

setUp()正如你所见,我们每一个 TestCase都需要这个tearDown()。所以,让我们把这个逻辑移到一个新文件,我们称之为BaseCase.py

#~/movie-bag/tests/BaseCase.py

import unittest

from app import app
from database.db import db


class BaseCase(unittest.TestCase):

    def setUp(self):
        self.app = app.test_client()
        self.db = db.get_db()


    def tearDown(self):
        # Delete Database collections after the test is complete
        for collection in self.db.list_collection_names():
            self.db.drop_collection(collection)

现在更新您的test_signup.py内容如下:


 import json

-from app import app
-from database.db import db
+from tests.BaseCase import BaseCase

-
-class SignupTest(unittest.TestCase):
+class SignupTest(BaseCase):
-
-    def setUp(self):
-        self.app = app.test_client()
-        self.db = db.get_db()

     def test_successful_signup(self):
         # Given
...
-
-    def tearDown(self):
-        # Delete Database collections after the test is complete
-        for collection in self.db.list_collection_names():
-            self.db.drop_collection(collection)

现在让我们为我们的功能添加测试,使用以下代码在文件夹Login创建一个新文件。test_login.pytests

#~/movie-bag/tests/test_login.py

import json

from tests.BaseCase import BaseCase

class TestUserLogin(BaseCase):

    def test_successful_login(self):
        # Given
        email = "paurakh011@gmail.com"
        password = "mycoolpassword"
        payload = json.dumps({
            "email": email,
            "password": password
        })
        response = self.app.post('/api/auth/signup', headers={"Content-Type": "application/json"}, data=payload)

        # When
        response = self.app.post('/api/auth/login', headers={"Content-Type": "application/json"}, data=payload)

        # Then
        self.assertEqual(str, type(response.json['token']))
        self.assertEqual(200, response.status_code)

在这里,我们首先使用相同的电子邮件和密码创建具有端点的用户/api/auth/signup并登录,并断言/api/auth/login端点返回令牌。

现在,让我们添加测试来检查影片的创建。使用以下代码
创建。test_create_movie.py

#movie-bag/tests/test_create_movie.py

import json

from tests.BaseCase import BaseCase

class TestUserLogin(BaseCase):

    def test_successful_login(self):
        # Given
        email = "paurakh011@gmail.com"
        password = "mycoolpassword"
        user_payload = json.dumps({
            "email": email,
            "password": password
        })

        self.app.post('/api/auth/signup', headers={"Content-Type": "application/json"}, data=user_payload)
        response = self.app.post('/api/auth/login', headers={"Content-Type": "application/json"}, data=user_payload)
        login_token = response.json['token']

        movie_payload = {
            "name": "Star Wars: The Rise of Skywalker",
            "casts": ["Daisy Ridley", "Adam Driver"],
            "genres": ["Fantasy", "Sci-fi"]
        }
        # When
        response = self.app.post('/api/movies',
            headers={"Content-Type": "application/json", "Authorization": f"Bearer {login_token}"},
            data=json.dumps(movie_payload))

        # Then
        self.assertEqual(str, type(response.json['id']))
        self.assertEqual(200, response.status_code)

要一次运行所有测试,请使用以下命令:

python -m unittest --buffer

这里--buffer-b用于在成功测试运行时丢弃输出。

这里我们首先注册用户账户,以用户身份登录获取登录令牌,然后使用登录令牌创建电影。最后,我们检查电影创建端点是否返回id已创建电影的 。

你可能已经注意到,在这个测试中,我们只检查影片创建是否成功,但没有检查用户创建或用户登录是否成功。这是因为我们已经有单独的测试来测试这些功能,所以不必重复进行相同的测试。

我们只创建了快乐路径测试,但测试即使在用户输入无效的情况下,应用程序的响应是否符合预期对我们来说至关重要。例如,用户在注册时没有发送密码,或者发送了格式无效的电子邮件。

我没有在教程本身中包含这些测试,但我一定会将它们包含在 Github 仓库中。

您可以在这里找到我们迄今为止编写的所有代码和更多测试

我们从本系列的这一部分中学到了什么?

  • 为什么我们应该为我们的应用程序编写测试
  • 什么是测试隔离以及为什么我们应该隔离测试用例
  • 如何使用unittest

请随意在下面的评论中添加我在这篇文章中遗漏的任何内容。

如果你有任何主题建议,请告诉我。希望下次再见。

在那之前,你可以在推特上关注我

文章来源:https://dev.to/paurakhsharma/flask-rest-api-part-6-testing-rest-apis-4lla
PREV
2024 年你应该了解的 7 个 Web 开发堆栈!
NEXT
Flask Rest API -第三部分-身份验证和授权