feat: 员工信息管理功能完善
- 将员工表org_no字段迁移至dept_id,关联系统部门表 - 更新员工信息相关DTO、VO和Controller,使用deptId替代orgNo - 添加员工信息管理OpenSpec规范文档(proposal/design/spec/tasks) - 更新API文档,反映部门关联变更 - 添加数据库迁移脚本employee_org_no_to_dept_id.sql - 新增员工信息分页接口测试脚本(PowerShell/Python) - 更新CLAUDE.md,添加MCP数据库工具使用说明 Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
This commit is contained in:
437
test/test_pagination.py
Normal file
437
test/test_pagination.py
Normal file
@@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""分页接口总数测试脚本
|
||||
测试接口:
|
||||
1. /dpc/employee/list - 员工列表(MyBatis Plus分页)
|
||||
2. /dpc/intermediary/list - 中介黑名单列表(若依startPage分页)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import io
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 设置stdout编码为UTF-8
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
BASE_URL = "http://localhost:8080"
|
||||
LOGIN_URL = f"{BASE_URL}/login/test"
|
||||
EMPLOYEE_LIST_URL = f"{BASE_URL}/dpc/employee/list"
|
||||
INTERMEDIARY_LIST_URL = f"{BASE_URL}/dpc/intermediary/list"
|
||||
|
||||
# 测试结果存储
|
||||
test_results = []
|
||||
|
||||
|
||||
def login():
|
||||
"""登录获取token"""
|
||||
print("=" * 60)
|
||||
print("步骤1: 获取认证Token")
|
||||
print("=" * 60)
|
||||
|
||||
login_body = {
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
|
||||
try:
|
||||
# 使用json参数发送JSON格式请求体
|
||||
response = requests.post(LOGIN_URL, json=login_body)
|
||||
print(f"请求URL: {LOGIN_URL}")
|
||||
print(f"请求参数: {login_body}")
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
|
||||
data = response.json()
|
||||
if data.get("code") == 200:
|
||||
token = data.get("token")
|
||||
print(f"✓ Token获取成功: {token[:20]}...")
|
||||
return token
|
||||
else:
|
||||
print(f"✗ Token获取失败: {data.get('msg')}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"✗ 异常: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def test_page(url, token, page_num, page_size, test_name, api_type):
|
||||
"""测试指定分页参数的接口"""
|
||||
print(f"\n========== 测试: {test_name} ==========")
|
||||
print(f"API类型: {api_type}")
|
||||
print(f"URL: {url}")
|
||||
print(f"参数: pageNum={page_num}, pageSize={page_size}")
|
||||
|
||||
params = {
|
||||
"pageNum": page_num,
|
||||
"pageSize": page_size
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
print(f"响应状态码: {response.status_code}")
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"✗ HTTP错误: {response.text}")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "FAIL",
|
||||
"error": f"HTTP {response.status_code}"
|
||||
})
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
print(f"响应内容:\n{json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||
|
||||
code = data.get("code", 0)
|
||||
|
||||
if code != 200:
|
||||
print(f"✗ 业务错误: {data.get('msg')}")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "FAIL",
|
||||
"error": data.get("msg", "Unknown error")
|
||||
})
|
||||
return None
|
||||
|
||||
# 检查响应结构 - 支持两种格式
|
||||
# 格式1: {data: {total, rows}, code, msg}
|
||||
# 格式2: {total, rows, code, msg}
|
||||
if "data" in data:
|
||||
response_data = data["data"]
|
||||
rows = response_data.get("rows", [])
|
||||
total = response_data.get("total")
|
||||
else:
|
||||
# 扁平结构格式
|
||||
rows = data.get("rows", [])
|
||||
total = data.get("total")
|
||||
|
||||
rows_count = len(rows)
|
||||
print(f"\n--- 分页数据分析 ---")
|
||||
print(f"返回行数(rows): {rows_count}")
|
||||
print(f"总数(total): {total}")
|
||||
|
||||
# 验证total字段
|
||||
if total is None:
|
||||
print(f"✗ 响应缺少total字段")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "FAIL",
|
||||
"error": "响应缺少total字段"
|
||||
})
|
||||
return None
|
||||
|
||||
if not isinstance(total, int):
|
||||
print(f"✗ total类型错误: {type(total)}")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "FAIL",
|
||||
"error": f"total类型错误: {type(total)}"
|
||||
})
|
||||
return None
|
||||
|
||||
if total < 0:
|
||||
print(f"✗ total值无效: {total}")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "FAIL",
|
||||
"error": f"total值无效: {total}"
|
||||
})
|
||||
return None
|
||||
|
||||
# 计算预期值
|
||||
expected_total_pages = (total + page_size - 1) // page_size
|
||||
if page_num < expected_total_pages:
|
||||
expected_rows = page_size
|
||||
elif page_num == expected_total_pages:
|
||||
expected_rows = total - (page_size * (page_num - 1))
|
||||
else:
|
||||
expected_rows = 0
|
||||
|
||||
print(f"预期行数: {expected_rows}")
|
||||
print(f"预期总页数: {expected_total_pages}")
|
||||
|
||||
# 验证行数是否正确
|
||||
is_correct = rows_count == expected_rows
|
||||
|
||||
if is_correct:
|
||||
print(f"✓ 测试通过 - 分页总数返回正常")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "PASS",
|
||||
"page_num": page_num,
|
||||
"page_size": page_size,
|
||||
"rows_count": rows_count,
|
||||
"total": total,
|
||||
"expected_rows": expected_rows
|
||||
})
|
||||
else:
|
||||
print(f"✗ 测试失败 - 预期{expected_rows}行,实际{rows_count}行")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "FAIL",
|
||||
"page_num": page_num,
|
||||
"page_size": page_size,
|
||||
"rows_count": rows_count,
|
||||
"total": total,
|
||||
"expected_rows": expected_rows,
|
||||
"error": f"行数不匹配"
|
||||
})
|
||||
|
||||
# 显示返回的ID
|
||||
if rows:
|
||||
if "employeeId" in rows[0]:
|
||||
ids = ', '.join([str(r.get("employeeId")) for r in rows])
|
||||
print(f"员工ID: {ids}")
|
||||
elif "intermediaryId" in rows[0]:
|
||||
ids = ', '.join([str(r.get("intermediaryId")) for r in rows])
|
||||
print(f"中介ID: {ids}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"rows": rows_count,
|
||||
"total": total,
|
||||
"expected_rows": expected_rows,
|
||||
"is_correct": is_correct
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 异常: {e}")
|
||||
test_results.append({
|
||||
"test_name": test_name,
|
||||
"api_type": api_type,
|
||||
"status": "ERROR",
|
||||
"error": str(e)
|
||||
})
|
||||
return None
|
||||
|
||||
|
||||
def test_consistency(url, token, test_name, api_type):
|
||||
"""测试不同pageSize下total是否一致"""
|
||||
print(f"\n========== 测试总数一致性: {test_name} ==========")
|
||||
|
||||
page_sizes = [10, 20, 50]
|
||||
totals = []
|
||||
|
||||
for size in page_sizes:
|
||||
params = {"pageNum": 1, "pageSize": size}
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get("code") == 200:
|
||||
# 支持两种响应格式
|
||||
if "data" in data:
|
||||
total = data.get("data", {}).get("total")
|
||||
else:
|
||||
total = data.get("total")
|
||||
totals.append(total)
|
||||
print(f"pageSize={size}: total={total}")
|
||||
except Exception as e:
|
||||
print(f"✗ 异常: {e}")
|
||||
|
||||
if len(set(totals)) == 1 and totals[0] is not None:
|
||||
print(f"✓ 不同pageSize下总数一致: {totals[0]}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 不同pageSize下总数不一致: {totals}")
|
||||
return False
|
||||
|
||||
|
||||
def generate_report():
|
||||
"""生成测试报告"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试报告")
|
||||
print("=" * 60)
|
||||
|
||||
# 按API类型分组
|
||||
employee_results = [r for r in test_results if "员工" in r.get("test_name", "")]
|
||||
intermediary_results = [r for r in test_results if "中介" in r.get("test_name", "")]
|
||||
|
||||
pass_count = sum(1 for r in test_results if r["status"] == "PASS")
|
||||
fail_count = sum(1 for r in test_results if r["status"] == "FAIL")
|
||||
error_count = sum(1 for r in test_results if r["status"] == "ERROR")
|
||||
|
||||
print(f"\n总测试数: {len(test_results)}")
|
||||
print(f"通过: {pass_count}")
|
||||
print(f"失败: {fail_count}")
|
||||
print(f"错误: {error_count}")
|
||||
|
||||
# 员工接口结果
|
||||
print(f"\n--- 员工列表接口 (MyBatis Plus) ---")
|
||||
print(f"测试数: {len(employee_results)}")
|
||||
for r in employee_results:
|
||||
status_icon = "✓" if r["status"] == "PASS" else "✗"
|
||||
print(f"{status_icon} {r['test_name']}: {r['status']}")
|
||||
if r["status"] == "PASS":
|
||||
print(f" 页码: {r.get('page_num')}/{r.get('page_size')}, "
|
||||
f"返回行数: {r.get('rows_count')}, 总数: {r.get('total')}")
|
||||
else:
|
||||
print(f" 错误: {r.get('error', 'Unknown')}")
|
||||
|
||||
# 中介黑名单接口结果
|
||||
print(f"\n--- 中介黑名单接口 (若依startPage) ---")
|
||||
print(f"测试数: {len(intermediary_results)}")
|
||||
for r in intermediary_results:
|
||||
status_icon = "✓" if r["status"] == "PASS" else "✗"
|
||||
print(f"{status_icon} {r['test_name']}: {r['status']}")
|
||||
if r["status"] == "PASS":
|
||||
print(f" 页码: {r.get('page_num')}/{r.get('page_size')}, "
|
||||
f"返回行数: {r.get('rows_count')}, 总数: {r.get('total')}")
|
||||
else:
|
||||
print(f" 错误: {r.get('error', 'Unknown')}")
|
||||
|
||||
# 总体结论
|
||||
print(f"\n--- 测试结论 ---")
|
||||
if fail_count == 0 and error_count == 0:
|
||||
print("✓ 所有分页接口总数返回正常")
|
||||
else:
|
||||
print("✗ 存在分页接口总数返回异常")
|
||||
|
||||
# 保存报告到文件
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
report_file = f"test/pagination_test_report_{timestamp}.txt"
|
||||
|
||||
with open(report_file, "w", encoding="utf-8") as f:
|
||||
f.write("=" * 60 + "\n")
|
||||
f.write("分页接口总数测试报告\n")
|
||||
f.write("=" * 60 + "\n\n")
|
||||
f.write(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||
|
||||
f.write("测试统计:\n")
|
||||
f.write(f" 总测试数: {len(test_results)}\n")
|
||||
f.write(f" 通过: {pass_count}\n")
|
||||
f.write(f" 失败: {fail_count}\n")
|
||||
f.write(f" 错误: {error_count}\n\n")
|
||||
|
||||
f.write("测试接口:\n")
|
||||
f.write(" 1. /dpc/employee/list - 员工列表(MyBatis Plus分页)\n")
|
||||
f.write(" 2. /dpc/intermediary/list - 中介黑名单列表(若依startPage分页)\n\n")
|
||||
|
||||
f.write("-" * 60 + "\n")
|
||||
f.write("详细结果:\n")
|
||||
f.write("-" * 60 + "\n\n")
|
||||
|
||||
for r in test_results:
|
||||
f.write(f"测试: {r['test_name']}\n")
|
||||
f.write(f"API类型: {r['api_type']}\n")
|
||||
f.write(f"状态: {r['status']}\n")
|
||||
if r['status'] == 'PASS':
|
||||
f.write(f" 页码: {r.get('page_num')}/{r.get('page_size')}\n")
|
||||
f.write(f" 返回行数: {r.get('rows_count')}\n")
|
||||
f.write(f" 总数: {r.get('total')}\n")
|
||||
f.write(f" 预期行数: {r.get('expected_rows')}\n")
|
||||
else:
|
||||
f.write(f" 错误: {r.get('error', 'Unknown')}\n")
|
||||
f.write("\n")
|
||||
|
||||
f.write("-" * 60 + "\n")
|
||||
f.write("测试结论:\n")
|
||||
if fail_count == 0 and error_count == 0:
|
||||
f.write("✓ 所有分页接口总数返回正常\n")
|
||||
else:
|
||||
f.write("✗ 存在分页接口总数返回异常\n")
|
||||
|
||||
print(f"\n报告已保存至: {report_file}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("\n" + "=" * 60)
|
||||
print("分页接口总数测试")
|
||||
print("=" * 60)
|
||||
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# 获取token
|
||||
token = login()
|
||||
if not token:
|
||||
print("\n✗ 无法获取token,测试终止")
|
||||
return
|
||||
|
||||
print("\n✓ 登录成功,开始测试")
|
||||
|
||||
# 员工列表接口测试用例
|
||||
employee_test_cases = [
|
||||
{"page_num": 1, "page_size": 10, "desc": "员工列表 - 第1页(10条/页)"},
|
||||
{"page_num": 2, "page_size": 10, "desc": "员工列表 - 第2页(10条/页)"},
|
||||
{"page_num": 1, "page_size": 5, "desc": "员工列表 - 第1页(5条/页)"},
|
||||
{"page_num": 1, "page_size": 20, "desc": "员工列表 - 第1页(20条/页)"},
|
||||
]
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("测试员工列表接口(MyBatis Plus分页)")
|
||||
print("=" * 60)
|
||||
|
||||
for test_case in employee_test_cases:
|
||||
test_page(
|
||||
EMPLOYEE_LIST_URL,
|
||||
token,
|
||||
test_case["page_num"],
|
||||
test_case["page_size"],
|
||||
test_case["desc"],
|
||||
"MyBatis Plus"
|
||||
)
|
||||
|
||||
# 测试总数一致性
|
||||
test_consistency(
|
||||
EMPLOYEE_LIST_URL,
|
||||
token,
|
||||
"员工列表-总数一致性",
|
||||
"MyBatis Plus"
|
||||
)
|
||||
|
||||
# 中介黑名单接口测试用例
|
||||
intermediary_test_cases = [
|
||||
{"page_num": 1, "page_size": 10, "desc": "中介黑名单 - 第1页(10条/页)"},
|
||||
{"page_num": 2, "page_size": 10, "desc": "中介黑名单 - 第2页(10条/页)"},
|
||||
{"page_num": 1, "page_size": 5, "desc": "中介黑名单 - 第1页(5条/页)"},
|
||||
{"page_num": 1, "page_size": 20, "desc": "中介黑名单 - 第1页(20条/页)"},
|
||||
]
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("测试中介黑名单接口(若依startPage分页)")
|
||||
print("=" * 60)
|
||||
|
||||
for test_case in intermediary_test_cases:
|
||||
test_page(
|
||||
INTERMEDIARY_LIST_URL,
|
||||
token,
|
||||
test_case["page_num"],
|
||||
test_case["page_size"],
|
||||
test_case["desc"],
|
||||
"若依startPage"
|
||||
)
|
||||
|
||||
# 测试总数一致性
|
||||
test_consistency(
|
||||
INTERMEDIARY_LIST_URL,
|
||||
token,
|
||||
"中介黑名单-总数一致性",
|
||||
"若依startPage"
|
||||
)
|
||||
|
||||
# 生成报告
|
||||
generate_report()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("测试完成")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user