接口自动化测试是保障服务质量的基石。很多测试同学会写用例脚本,但当用例量激增时,如何管理用例、如何封装请求、如何生成优雅的测试报告,往往显得手忙脚乱。
市面上有很多成熟的测试框架,但“造轮子”是理解自动化精髓的最佳路径。本文将带你从零开始,基于 Python + Pytest + Requests,手撸一个轻量级、企业级的接口自动化测试框架。通过代码实战,你将掌握数据驱动、封装设计等核心技能。
一、 框架架构设计
一个专业的自动化框架,目录结构必须清晰,职责必须分离。
复制
auto_test_framework/
├── config/ # 配置文件
│ └── config.yaml
├── api/ # 接口封装层(业务逻辑)
│ └── user_api.py
├── cases/ # 测试用例层
│ └── test_user_login.py
├── utils/ # 工具类(封装请求、读取文件)
│ ├── client.py # 核心:二次封装 Requests
│ └── logger.py # 日志工具
├── data/ # 测试数据(JSON/YAML)
│ └── login_data.json
└── reports/ # 测试报告生成目录
核心思路:
- API 层:封装具体的业务接口,如登录、下单。
- Client 层:底层封装
requests,统一处理 Session、Cookie、日志、异常。 - Case 层:使用 Pytest,结合数据驱动,只写测试逻辑。
二、 核心:二次封装 Requests 会话
直接在用例里写 requests.get() 会导致代码冗余且难以维护。我们需要一个统一的 HTTP 客户端,集成日志记录和 Session 管理。
1. utils/client.py 封装实现
python
复制
import requests
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class HttpClient:
def __init__(self, base_url):
self.base_url = base_url
# 使用 Session 自动管理 Cookies,保持会话状态
self.session = requests.Session()
def request(self, method, endpoint, **kwargs):
"""
统一请求方法封装
:param method: 请求方法 GET/POST/PUT/DELETE
:param endpoint: 接口地址(不包含域名)
:param kwargs: requests 支持的所有参数
:return: 响应对象
"""
url = f"{self.base_url}{endpoint}"
# 打印请求信息,方便调试
logger.info(f"Request: {method} {url}")
logger.info(f"Params: {kwargs.get('params') or kwargs.get('json')}")
try:
# 发送请求
res = self.session.request(method, url, **kwargs)
# 记录响应信息
logger.info(f"Response Status: {res.status_code}")
logger.info(f"Response Body: {res.text}")
return res
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
raise
三、 业务层:API 接口封装
将具体的接口调用逻辑与测试脚本剥离。如果在多个用例中都需要“登录”,只需要在这里维护一份代码。
2. api/user_api.py 用户接口封装
python
复制
from utils.client import HttpClient
class UserAPI:
def __init__(self, client: HttpClient):
self.client = client
def login(self, username, password):
"""
登录接口
"""
payload = {
"username": username,
"password": password
}
# 发送 POST 请求
return self.client.request("POST", "/api/v1/login", json=payload)
def get_user_info(self, user_id):
"""
获取用户信息接口
"""
params = {"id": user_id}
return self.client.request("GET", "/api/v1/user/info", params=params)
四、 数据驱动:Pytest 参数化实战
为了实现“一份代码测多组数据”,我们将测试数据存储在 JSON 文件中,通过 Pytest 的 @pytest.mark.parametrize 加载。
3. data/login_data.json 测试数据
json
复制
[ { "desc": "正确账号密码登录", "username": "admin", "password": "123456", "expected_code": 200, "expected_msg": "success" }, { "desc": "密码错误", "username": "admin", "password": "wrong_pwd", "expected_code": 401, "expected_msg": "Unauthorized" }, { "desc": "用户名为空", "username": "", "password": "123456", "expected_code": 400, "expected_msg": "Bad Request" }]
4. cases/test_user_login.py 测试用例编写
python
复制
import pytest
import json
from utils.client import HttpClient
from api.user_api import UserAPI
# 读取测试数据
def load_json_data(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
# 初始化 API 客户端(这里假设本地 Mock 服务,实际应为配置文件读取环境变量)
client = HttpClient(base_url="http://127.0.0.1:5000")
user_api = UserAPI(client)
# 获取测试数据
test_data = load_json_data("../data/login_data.json")
class TestUserLogin:
@pytest.mark.parametrize("case", test_data)
def test_login(self, case):
"""
测试登录功能
:param case: 从 json 加载的每一行数据字典
"""
# 1. 步骤:调用接口
res = user_api.login(case["username"], case["password"])
# 2. 断言:校验状态码
assert res.status_code == case["expected_code"], \
f"{case['desc']}:状态码不一致,期望 {case['expected_code']},实际 {res.status_code}"
# 3. 断言:校验返回体 (这里简单演示,实际可能需要校验 JSON 结构)
res_json = res.json()
assert res_json.get("message") == case["expected_msg"], \
f"{case['desc']}:提示信息不一致"
print(f"✅ 用例通过:{case['desc']}")
五、 框架进阶:Hook 与 配置
为了让框架更专业,我们需要在 Pytest 中使用 conftest.py 来做全局的前后置处理,比如初始化日志、清理测试数据、生成 Allure 报告。
5. conftest.py 钩子函数
python
复制
import pytest
from utils.client import HttpClient
@pytest.fixture(scope="session")
def global_client():
"""
Session 级别的 Fixture:
在整个测试会话开始时创建 Client,结束后销毁
可以在这里实现登录获取 Token,并注入到 Client 中
"""
print("\n[Setup] 初始化全局 HTTP 客户端...")
client = HttpClient(base_url="http://127.0.0.1:5000")
# 例子:预先登录,获取 token 并放入 session headers
# login_res = client.request("POST", "/api/v1/login", json={"u":"a","p":"b"})
# token = login_res.json().get("token")
# client.session.headers.update({"Authorization": f"Bearer {token}"})
yield client # 将 client 传递给用例
print("\n[Teardown] 清理资源...")
然后在 test_user_login.py 中修改测试类,使用 fixture 注入:
python
复制
class TestUserLogin:
def test_login(self, global_client, case): # Pytest 自动注入 global_client
# 使用注入的 client 实例化 API
api = UserAPI(global_client)
res = api.login(case["username"], case["password"])
assert res.status_code == case["expected_code"]
六、 总结
通过以上实战,我们搭建了一个具备以下特性的专业框架:
- 分层架构:API 层与 Case 层分离,维护成本大幅降低。
- 统一封装:
HttpClient解决了日志记录和会话管理问题,代码复用性高。 - 数据驱动:JSON + Pytest 参数化,实现了“代码逻辑”与“测试数据”的彻底分离。
这就摆脱了“脚本小子”的范畴,迈向了测试开发的领域。掌握这套思路,无论是 Python 还是 Java,你都能轻松构建出适合自己团队的自动化测试平台。