Flask Rest API -第 6 部分- 测试 REST API
第 6 部分:测试 REST API
你好!在本系列的上一篇中,我们学习了如何
在 REST API 中执行密码重置。
在本部分中,我们将学习如何测试我们的 REST API 端点。
我们为什么要花时间编写测试?
- 确保我们的应用程序在进行更改/重构时不会中断
- 自动化重复的手动测试,减少人为错误
- 能够在周五发布生产;)
- 测试提供了更好的 CI/CD 工作流程。
希望你确信我们应该编写测试。让我们开始测试我们的 Flask 应用程序。
说到测试,有两种最流行的工具可以测试 Python 应用程序。1
)unittest:unittest
是一个 Python标准库,这意味着它与 Python 一起分发。unittest
提供了大量用于构建和运行测试的工具。
2) pytest:pytest
是一个 Python 库,我喜欢称它为 的超集,这意味着你可以运行用unittest
编写的测试。它使编写测试变得更容易、更快捷。unittest
pytest
在本教程中,我们将学习如何使用编写测试,因为它使我们能够使用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.TestCase
。TestCase
它为我们提供有用的方法,例如setUp
和tearDown
以及断言方法。
setUp()
方法每次在运行类中定义的每个方法之前都会运行SignupTest
。setUp()
顾名思义,它用于在运行测试之前设置测试基础架构。
在这里您可以看到我们在此方法中定义了this.app
和this.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.py
tests
#~/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