在测试框架中,与数据库的交互是一种常见需求,特别是在接口测试中,可能需要验证接口操作是否正确反映到数据库中,或者从数据库中查询数据,作为接口请求的参数。
以下是一个结合 pytest 测试框架、MySQL 数据库(兼容 OB-MySQL)、以及之前的 YML 用例测试框架 的完整解决方案。
1. 需求分析
1.1 数据库交互场景
- 前置数据准备:
- 在接口测试之前,需要向数据库插入测试数据。
- 接口参数化:
- 从数据库中查询特定字段值,动态注入到接口请求参数中。
- 后置验证:
- 测试接口调用后,验证数据库中的数据是否符合期望。
1.2 设计目标
- 集成 MySQL 数据库(兼容 OB-MySQL)。
- 支持从数据库读取字段作为接口参数。
- 支持在用例中定义数据库操作(如查询语句、验证规则等)。
- 保持与之前的 YML 用例框架兼容。
2. 数据库交互设计
使用 PyMySQL 库与 MySQL 数据库进行交互。
2.1 数据库工具类
实现一个数据库操作工具类,用于执行 SQL 查询、插入数据和验证数据。
import pymysql
class MySQLHandler:
def __init__(self, host, port, user, password, database):
self.connection = pymysql.connect(
host=host,
port=port,
user=user,
password=password,
database=database,
cursorclass=pymysql.cursors.DictCursor
)
def execute_query(self, query, params=None):
"""
执行查询语句
:param query: SQL 查询语句
:param params: 查询参数
:return: 查询结果
"""
with self.connection.cursor() as cursor:
cursor.execute(query, params)
result = cursor.fetchall()
return result
def execute_update(self, query, params=None):
"""
执行插入或更新语句
:param query: SQL 更新语句
:param params: 更新参数
"""
with self.connection.cursor() as cursor:
cursor.execute(query, params)
self.connection.commit()
def close(self):
"""关闭数据库连接"""
self.connection.close()
3. YML 用例扩展
在 YML 用例中增加对数据库操作的支持,包括:
- 动态参数注入:通过数据库查询结果动态填充接口请求参数。
- 数据库验证:通过 SQL 查询验证接口调用后的数据状态。
3.1 用例格式示例
扩展后的 YML 用例示例:
test_cases:
- name: "用户登录"
request:
method: POST
url: "http://example.com/api/login"
headers:
Content-Type: "application/json"
body:
username: "$db.query.username" # 从数据库查询结果动态注入
password: "test_password"
expected:
status_code: 200
body:
token: "$response.data.token"
save:
token: "data.token"
db_verify:
- query: "SELECT * FROM users WHERE username = %s"
params: ["$db.query.username"]
expected:
- field: "status"
value: "active"
- name: "获取用户信息"
request:
method: GET
url: "http://example.com/api/users/$global.user_id"
headers:
Authorization: "Bearer $global.token"
expected:
status_code: 200
body:
user_id: "$global.user_id"
username: "$db.query.username"
3.2 关键字段说明
$db.query.field:- 从数据库查询结果中动态注入字段值。
db_verify:- 用于指定接口后的数据库验证规则,包括查询语句、参数和期望结果。
4. 测试框架集成
将数据库交互功能集成到之前的 pytest 测试框架中。
4.1 集成代码示例
完整测试框架代码
import pytest
import requests
import yaml
from pymysql import MySQLError
from MySQLHandler import MySQLHandler # 导入数据库工具类
# 全局上下文
context = {
"request": {},
"response": {},
"global": {},
"db": {}
}
# 初始化数据库连接
db_handler = MySQLHandler(
host="localhost",
port=3306,
user="root",
password="password",
database="test_db"
)
# 加载测试用例
def load_test_cases(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)["test_cases"]
# 保存动态数据到上下文
def save_to_context(response, context, save_mapping):
for key, path in save_mapping.items():
value = get_nested_value(response, path.split('.'))
if value is not None:
context["global"][key] = value
# 解析占位符
def resolve_placeholders(data, context):
if isinstance(data, str):
matches = re.findall(r"\$([\w\.]+)", data)
for match in matches:
# 支持从上下文和数据库中获取值
if match.startswith("db.query"):
keys = match.split('.')
value = context["db"].get(keys[-1])
else:
keys = match.split('.')
value = get_nested_value(context, keys)
if value is not None:
data = data.replace(f"${{{match}}}", str(value))
return data
elif isinstance(data, dict):
return {k: resolve_placeholders(v, context) for k, v in data.items()}
elif isinstance(data, list):
return [resolve_placeholders(item, context) for item in data]
else:
return data
# 数据库验证
def verify_database_rules(db_rules, context):
for rule in db_rules:
query = resolve_placeholders(rule["query"], context)
params = resolve_placeholders(rule.get("params", []), context)
expected_results = rule["expected"]
# 执行数据库查询
try:
results = db_handler.execute_query(query, params)
if not results:
raise AssertionError(f"Database query returned no results: {query}")
# 验证字段值
for expected in expected_results:
field = expected["field"]
expected_value = expected["value"]
actual_value = results[0].get(field)
assert actual_value == expected_value, f"Expected {field}={expected_value}, but got {actual_value}"
except MySQLError as e:
raise AssertionError(f"Database query failed: {e}")
# 运行测试用例
@pytest.mark.parametrize("case", load_test_cases("test_cases.yml"))
def test_api(case):
# 1. 解析请求参数
request_data = resolve_placeholders(case["request"], context)
# 2. 发送请求
response = requests.request(
method=request_data["method"],
url=request_data["url"],
headers=request_data.get("headers"),
json=request_data.get("body")
)
# 3. 保存请求和响应数据到上下文
context["request"][case["name"]] = request_data
context["response"][case["name"]] = response.json()
# 4. 保存动态数据
save_mapping = case.get("save", {})
save_to_context(response.json(), context, save_mapping)
# 5. 验证数据库规则
if "db_verify" in case:
verify_database_rules(case["db_verify"], context)
# 6. 验证响应
expected_data = resolve_placeholders(case["expected"], context)
assert response.status_code == expected_data["status_code"]
for key, value in expected_data.get("body", {}).items():
assert value == get_nested_value(response.json(), key.split('.'))
# 关闭数据库连接
@pytest.fixture(scope="session", autouse=True)
def close_db_connection():
yield
db_handler.close()
4.2 测试用例执行
- 准备测试用例 YML 文件:
将测试用例保存在
test_cases.yml文件中。 - 运行测试:
使用 pytest 执行测试:
pytest -v
5. 示例说明
5.1 数据库表结构
假设测试的数据库表结构如下:
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(50) NOT NULL,
status VARCHAR(20) DEFAULT 'inactive'
);
5.2 测试目标
- 插入用户数据。
- 调用登录接口。
- 验证接口返回的 Token。
- 确认数据库中用户状态为
active。
6. 总结
通过以上设计,测试框架实现了以下功能:
- 数据库交互:
- 支持查询、插入、更新操作。
- 参数化注入:
- 从数据库结果动态填充接口参数。
- 数据库验证:
- 验证接口调用后的数据库状态。
- YML 用例集成:
- 保持用例的可读性和扩展性。
这种设计适合复杂的业务场景,特别是接口测试依赖数据库操作的场景。