pytest如何进行关联接口调用
·
在 pytest 中进行关联接口调用,最核心、最推荐的方式是使用 Fixtures(测试夹具)。Fixtures 是 pytest 的精髓所在,它能完美地解决接口之间数据依赖和传递的问题。
下面我将通过一个典型的场景来详细解释如何操作:“先登录获取 token,再带着 token 去请求用户信息”。
核心思想
- 准备工作: 将前置接口(如登录)的调用封装在一个 fixture 中。
- 数据传递: fixture 运行后,使用
yield关键字将需要传递给后续接口的数据(如 token、用户ID)返回给测试用例。 - 依赖注入: 在测试用例的参数中声明需要用到的 fixture,pytest 会自动执行它并将
yield的数据传入。 - 清理工作: 如果需要,可以在
yield之后编写清理代码(如登出、删除测试数据),确保测试环境的纯净。
场景一:登录 -> 获取用户信息
这是一个最基础的两步关联。
1. 项目准备
首先,确保你已经安装了 pytest 和 requests:
pip install pytest requests
创建一个测试文件,例如 test_user_api.py。
# test_user_api.py
import pytest
import requests
# 假设这是你的API基础URL
BASE_URL = "https://api.example.com"
@pytest.fixture(scope="session") # scope="session"表示这个fixture在整个测试会话中只执行一次
def login_fixture():
"""
一个用于登录并获取token的fixture。
这个fixture在整个测试会话(session)中只会执行一次,
所有需要登录的测试用例都可以共享这个token。
"""
print("\n---【Setup】执行登录操作---")
# 1. 准备登录数据
login_data = {
"username": "testuser",
"password": "testpassword"
}
# 2. 发送登录请求
response = requests.post(f"{BASE_URL}/login", json=login_data)
# 3. 在fixture中进行断言,确保前置条件成功
assert response.status_code == 200
response_json = response.json()
assert "token" in response_json
# 4. 提取需要传递的数据
token = response_json["token"]
# 5. 使用yield将数据传递给测试用例
yield token
# --- yield之后的部分是Teardown,在所有测试用例执行完毕后运行 ---
print("\n---【Teardown】执行登出或清理操作---")
# 可以在这里添加登出接口的调用
# requests.post(f"{BASE_URL}/logout", headers={"Authorization": f"Bearer {token}"})
def test_get_user_info(login_fixture):
"""
测试获取用户信息的接口。
这个测试用例依赖于login_fixture。
"""
# 1. 从fixture接收token
# pytest会自动将login_fixture的yield值赋给同名参数`login_fixture`
token = login_fixture
print(f"---【Test】获取到的token是: {token[:10]}... ---")
# 2. 构造请求头
headers = {
"Authorization": f"Bearer {token}"
}
# 3. 发送获取用户信息的请求
response = requests.get(f"{BASE_URL}/user/profile", headers=headers)
# 4. 对最终结果进行断言
assert response.status_code == 200
user_info = response.json()
assert user_info["username"] == "testuser"
assert "email" in user_info
3. 如何运行和理解
在终端中运行 pytest:
pytest -s -v
-s: 为了能看到print的输出。-v: 显示详细信息。
你会看到如下的执行流程:
- pytest 发现
test_get_user_info需要login_fixture。 - pytest 执行
login_fixture。 login_fixture打印 "【Setup】执行登录操作",发送登录请求,并断言登录成功。login_fixture通过yield token将 token "暂停"并传递出去。- pytest 将 token 注入到
test_get_user_info函数的login_fixture参数中。 test_get_user_info函数体执行,使用 token 发起第二个请求并进行断言。- 当所有依赖
login_fixture的测试都执行完毕后(因为scope="session"),pytest会回到login_fixture中yield之后的部分,执行清理代码。
场景二:创建商品 -> 查询商品 -> 删除商品
这个场景更复杂,展示了 fixture 之间如何相互依赖,以及如何利用 fixture 的 teardown 机制来清理测试数据。
# test_product_api.py
import pytest
import requests
# 假设login_fixture定义在同项目下的conftest.py或当前文件中
# 这里为了演示,我们重新定义一个简单的
@pytest.fixture(scope="module")
def login_fixture():
# 假设登录成功并返回了token
yield "fake-admin-token-for-product-management"
@pytest.fixture(scope="function") # scope="function"表示每个测试函数都会重新执行一次
def create_product_fixture(login_fixture):
"""
创建商品的fixture,它依赖于login_fixture。
它会创建一个商品,将商品ID传递给测试用例,
然后在测试用例执行完毕后,自动删除这个商品。
"""
print("\n---【Setup】执行创建商品操作---")
token = login_fixture
headers = {"Authorization": f"Bearer {token}"}
product_data = {
"name": "测试商品",
"price": 99.99
}
response = requests.post(f"{BASE_URL}/products", json=product_data, headers=headers)
assert response.status_code == 201 # 201 Created
product_id = response.json()["id"]
print(f"---【Setup】成功创建商品,ID: {product_id}---")
# 使用yield将ID传递出去
yield product_id
# --- Teardown: 测试函数执行完毕后,删除刚刚创建的商品 ---
print(f"\n---【Teardown】执行删除商品操作,ID: {product_id}---")
delete_response = requests.delete(f"{BASE_URL}/products/{product_id}", headers=headers)
assert delete_response.status_code in [200, 204] # 200 OK or 204 No Content
# --- 测试用例 ---
def test_get_product_by_id(create_product_fixture, login_fixture):
"""
测试通过ID查询商品。
它依赖create_product_fixture来获取一个有效的商品ID。
"""
product_id = create_product_fixture # 从fixture获取商品ID
token = login_fixture # 也可以同时依赖登录fixture
print(f"---【Test】正在查询商品,ID: {product_id}---")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/products/{product_id}", headers=headers)
assert response.status_code == 200
assert response.json()["name"] == "测试商品"
def test_update_product(create_product_fixture, login_fixture):
"""
测试更新商品信息。
同样依赖create_product_fixture。
"""
product_id = create_product_fixture
token = login_fixture
print(f"---【Test】正在更新商品,ID: {product_id}---")
headers = {"Authorization": f"Bearer {token}"}
update_data = {"price": 129.99}
response = requests.patch(f"{BASE_URL}/products/{product_id}", json=update_data, headers=headers)
assert response.status_code == 200
assert response.json()["price"] == 129.99
执行流程分析 test_get_product_by_id
- pytest 发现测试需要
create_product_fixture和login_fixture。 create_product_fixture又依赖login_fixture,所以 pytest 先执行login_fixture。login_fixture返回 token。- pytest 执行
create_product_fixture,并将 token 传入。 create_product_fixture创建一个商品,yield出商品 ID。- pytest 将商品 ID 注入
test_get_product_by_id的create_product_fixture参数,并将 token 注入login_fixture参数。 - 测试函数
test_get_product_by_id执行。 - 测试函数执行完毕,pytest 返回到
create_product_fixture的yield之后,执行删除商品的代码。 - 因为
create_product_fixture的scope是function,所以test_update_product执行时会重复上述1-8的完整流程,确保每次测试都基于一个全新的商品。
总结与最佳实践
- 首选 Fixture: 忘记全局变量或在类中使用
self传递数据。Fixture 是 pytest 中处理依赖和状态传递的标准且最优雅的方式。 - 合理选择
scope:function: 每个测试函数都执行一次。适用于需要独立、干净数据的场景(如创建/删除)。class: 每个测试类执行一次。module: 每个模块(.py文件)执行一次。session: 整个测试会话(一次pytest命令)执行一次。非常适合登录、获取配置等全局性、开销大的操作。
- 在 Fixture 中断言: 对前置接口的响应进行断言,如果前置条件失败,测试会立即标记为
Error,而不会执行后续不必要的步骤,有助于快速定位问题。 - 利用
yield实现清理: 使用yield不仅是为了传递数据,更是为了利用其 teardown 机制来保证测试环境的清洁,避免测试用例之间相互影响。 - Fixture 依赖 Fixture: 复杂的流程可以通过 fixture 链式依赖来构建,使代码结构清晰,可读性高。
更多推荐

所有评论(0)