测试Flask应用¶
FlASK提供了用于测试应用程序的实用程序。本文档介绍了在测试中使用应用程序不同部分的技术。
我们将使用 pytest 框架来设置和运行我们的测试。
$ pip install pytest
这个 tutorial 回顾了如何为示例Flaskr博客应用程序编写100%覆盖率的测试。看见 the tutorial on tests 有关应用程序的特定测试的详细说明,请参见。
识别测试¶
测试通常位于 tests
文件夹。测试是以以下代码开头的函数 test_
,在以以下开头的Python模块中 test_
。测试还可以进一步分组到以 Test
。
可能很难知道要测试什么。通常,请尝试测试您编写的代码,而不是您使用的库的代码,因为它们已经过测试。尝试将复杂的行为提取为单独的函数进行单独测试。
固定装置¶
最高测试 fixtures 允许编写可跨测试重用的代码片段。一个简单的装置返回值,但装置也可以设置、产生一个值,然后进行拆卸。应用程序、测试客户端和CLI运行器的装置如下所示,它们可以放置在 tests/conftest.py
。
如果您正在使用 application factory ,定义一个 app
用于创建和配置应用程序实例的装置。方法之前和之后添加代码。 yield
若要设置和拆除其他资源,请执行以下操作:创建和清除数据库。
如果您没有使用工厂,那么您已经有了一个可以直接导入和配置的应用程序对象。您仍然可以使用 app
用于设置和拆卸资源的装置。
import pytest
from my_project import create_app
@pytest.fixture()
def app():
app = create_app()
app.config.update({
"TESTING": True,
})
# other setup can go here
yield app
# clean up / reset resources here
@pytest.fixture()
def client(app):
return app.test_client()
@pytest.fixture()
def runner(app):
return app.test_cli_runner()
使用测试客户端发送请求¶
测试客户端向应用程序发出请求,而无需运行实时服务器。FASK的客户扩展 Werkzeug's client 有关更多信息,请参阅这些文档。
这个 client
具有与常见的HTTP请求方法匹配的方法,例如 client.get()
和 client.post()
。它们采用了许多参数来构建请求;您可以在 EnvironBuilder
。通常情况下,您将使用 path
, query_string
, headers
,以及 data
或 json
。
要发出请求,请调用请求应使用的方法以及要测试的路由的路径。一个 TestResponse
以检查响应数据。它具有响应对象的所有常见属性。你通常会看到 response.data
,这是视图返回的字节数。如果您想使用文本,Werkzeug 2.1提供了 response.text
,或使用 response.get_data(as_text=True)
。
def test_request_example(client):
response = client.get("/posts")
assert b"<h2>Hello, World!</h2>" in response.data
宣判 query_string={"key": "value", ...}
在查询字符串中设置参数(在 ?
在URL中)。宣判 headers={}
若要设置请求头,请执行以下操作。
要在POST或PUT请求中发送请求正文,请将一个值传递给 data
。如果传递的是原始字节,则使用该正文。通常,您将传递一个字典来设置表单数据。
表单数据¶
要发送表单数据,请将字典传递到 data
。这个 Content-Type
标头将设置为 multipart/form-data
或 application/x-www-form-urlencoded
自动的。
如果值是为读取字节而打开的文件对象 ("rb"
模式),它将被视为上载的文件。若要更改检测到的文件名和内容类型,请传递 (file, filename, content_type)
元组。发出请求后,文件对象将被关闭,因此它们不需要使用通常的 with open() as f:
图案。
将文件存储在 tests/resources
文件夹,然后使用 pathlib.Path
若要获取相对于当前测试文件的文件,请执行以下操作。
from pathlib import Path
# get the resources folder in the tests folder
resources = Path(__file__).parent / "resources"
def test_edit_user(client):
response = client.post("/user/2/edit", data={
"name": "Flask",
"theme": "dark",
"picture": (resources / "picture.png").open("rb"),
})
assert response.status_code == 200
JSON数据¶
要发送JSON数据,请将对象传递给 json
。这个 Content-Type
标头将设置为 application/json
自动的。
同样,如果响应包含JSON数据,则 response.json
属性将包含反序列化的对象。
def test_json_data(client):
response = client.post("/graphql", json={
"query": """
query User($id: String!) {
user(id: $id) {
name
theme
picture_url
}
}
""",
variables={"id": 2},
})
assert response.json["data"]["user"]["name"] == "Flask"
以下重定向¶
默认情况下,如果响应是重定向,则客户端不会发出其他请求。通过路过 follow_redirects=True
对于请求方法,客户端将继续进行请求,直到返回非重定向响应。
TestResponse.history
是导致最终响应的响应的元组。每个响应都有一个 request
属性,该属性记录生成该响应的请求。
def test_logout_redirect(client):
response = client.get("/logout", follow_redirects=True)
# Check that there was one redirect response.
assert len(response.history) == 1
# Check that the second request was to the index page.
assert response.request.path == "/index"
访问和修改会话¶
访问FlASK的上下文变量,主要是 session
中使用客户端。 with
陈述。应用程序和请求上下文将保持活动状态 after 提出请求,直到 with
区块结束。
from flask import session
def test_access_session(client):
with client:
client.post("/auth/login", data={"username": "flask"})
# session is still accessible
assert session["user_id"] == 1
# session is no longer accessible
如果要访问或设置会话中的值 before 提出请求时,使用客户的 session_transaction()
方法中的 with
陈述。它返回一个会话对象,并在块结束后保存会话。
from flask import session
def test_modify_session(client):
with client.session_transaction() as session:
# set a user id without going through the login route
session["user_id"] = 1
# session is saved now
response = client.get("/users/me")
assert response.json["username"] == "flask"
使用CLI运行器运行命令¶
烧瓶提供 test_cli_runner()
要创建 FlaskCliRunner
,它以隔离方式运行CLI命令并在 Result
对象。烧瓶流道延伸 Click's runner 有关更多信息,请参阅这些文档。
使用跑步者的 invoke()
方法调用命令的方式与使用 flask
从命令行执行命令。
import click
@app.cli.command("hello")
@click.option("--name", default="World")
def hello_command(name):
click.echo(f"Hello, {name}!")
def test_hello_command(runner):
result = runner.invoke(args="hello")
assert "World" in result.output
result = runner.invoke(args=["hello", "--name", "Flask"])
assert "Flask" in result.output
依赖于活动上下文的测试¶
您可能具有从视图或命令调用的函数,这些函数需要活动的 application context 或 request context 因为他们可以访问 request
, session
,或 current_app
。您可以直接创建和激活上下文,而不是通过发出请求或调用命令来测试它们。
使用 with app.app_context()
若要推送应用程序上下文,请执行以下操作。例如,数据库扩展通常需要活动的应用程序上下文才能进行查询。
def test_db_post_model(app):
with app.app_context():
post = db.session.query(Post).get(1)
使用 with app.test_request_context()
要推送请求上下文,请执行以下操作。它接受与测试客户端的请求方法相同的参数。
def test_validate_user_edit(app):
with app.test_request_context(
"/user/2/edit", method="POST", data={"name": ""}
):
# call a function that accesses `request`
messages = validate_edit_user()
assert messages["name"][0] == "Name cannot be empty."
创建测试请求上下文不会运行任何Flask调度代码,因此 before_request
不调用函数。如果需要调用它们,通常更好的做法是发出完整的请求。但是,也可以手动呼叫它们。
def test_auth_token(app):
with app.test_request_context("/user/2/edit", headers={"X-Auth-Token": "1"}):
app.preprocess_request()
assert g.user.name == "Flask"