270 lines
9.5 KiB
Python
270 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
收入完整性检测算法测试脚本
|
||
自动运行所有测试场景并生成测试报告
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import json
|
||
import time
|
||
import requests
|
||
from datetime import datetime
|
||
from typing import Dict, List, Tuple
|
||
|
||
sys.path.append('/Users/liulujian/Documents/code/deeprisk-claude-1/backend')
|
||
|
||
from loguru import logger
|
||
|
||
logger.add("test_revenue_algorithm.log", rotation="100 MB", level="INFO")
|
||
|
||
class RevenueAlgorithmTester:
|
||
"""收入完整性检测算法测试器"""
|
||
|
||
def __init__(self, api_base: str = "http://localhost:8000/api/v1"):
|
||
self.api_base = api_base
|
||
self.test_results = []
|
||
self.passed_count = 0
|
||
self.failed_count = 0
|
||
|
||
def load_test_scenarios(self) -> Dict:
|
||
"""加载测试场景"""
|
||
scenarios_file = "/Users/liulujian/Documents/code/deeprisk-claude-1/backend/test_data/revenue_test/test_scenarios.json"
|
||
|
||
with open(scenarios_file, 'r', encoding='utf-8') as f:
|
||
return json.load(f)
|
||
|
||
def run_single_test(self, scenario_id: str, scenario: Dict) -> Dict:
|
||
"""运行单个测试场景"""
|
||
streamer_id = scenario['streamer_id']
|
||
expected_level = scenario['expected_risk_level']
|
||
|
||
logger.info(f"开始测试: {scenario_id} - {scenario['name']}")
|
||
|
||
try:
|
||
# 构建请求
|
||
request_data = {
|
||
"entity_id": streamer_id,
|
||
"entity_type": "streamer",
|
||
"period": "2024-01",
|
||
"rule_ids": ["REVENUE_INTEGRITY_CHECK"],
|
||
"parameters": {
|
||
"comparison_type": "monthly"
|
||
}
|
||
}
|
||
|
||
# 发送请求
|
||
start_time = time.time()
|
||
response = requests.post(
|
||
f"{self.api_base}/risk-detection/execute",
|
||
json=request_data,
|
||
timeout=30
|
||
)
|
||
end_time = time.time()
|
||
|
||
# 检查响应状态
|
||
if response.status_code != 200:
|
||
return {
|
||
"scenario_id": scenario_id,
|
||
"scenario_name": scenario['name'],
|
||
"streamer_id": streamer_id,
|
||
"status": "FAILED",
|
||
"error": f"HTTP {response.status_code}",
|
||
"expected_risk_level": expected_level,
|
||
"actual_risk_level": None,
|
||
"expected_risk_score": scenario['expected_risk_score'],
|
||
"actual_risk_score": None,
|
||
"duration_ms": int((end_time - start_time) * 1000),
|
||
"description": scenario['description']
|
||
}
|
||
|
||
# 解析响应
|
||
result = response.json()
|
||
|
||
# 提取结果
|
||
actual_level = result.get('risk_level')
|
||
actual_score = result.get('risk_score')
|
||
|
||
# 验证结果
|
||
is_passed = (actual_level == expected_level)
|
||
|
||
if is_passed:
|
||
self.passed_count += 1
|
||
logger.info(f"✓ 通过: {scenario_id}")
|
||
else:
|
||
self.failed_count += 1
|
||
logger.warning(f"✗ 失败: {scenario_id} - 预期 {expected_level}, 实际 {actual_level}")
|
||
|
||
return {
|
||
"scenario_id": scenario_id,
|
||
"scenario_name": scenario['name'],
|
||
"streamer_id": streamer_id,
|
||
"status": "PASSED" if is_passed else "FAILED",
|
||
"error": None,
|
||
"expected_risk_level": expected_level,
|
||
"actual_risk_level": actual_level,
|
||
"expected_risk_score": scenario['expected_risk_score'],
|
||
"actual_risk_score": actual_score,
|
||
"duration_ms": int((end_time - start_time) * 1000),
|
||
"description": scenario['description'],
|
||
"response": result
|
||
}
|
||
|
||
except Exception as e:
|
||
self.failed_count += 1
|
||
logger.error(f"✗ 异常: {scenario_id} - {str(e)}")
|
||
return {
|
||
"scenario_id": scenario_id,
|
||
"scenario_name": scenario['name'],
|
||
"streamer_id": streamer_id,
|
||
"status": "FAILED",
|
||
"error": str(e),
|
||
"expected_risk_level": expected_level,
|
||
"actual_risk_level": None,
|
||
"expected_risk_score": scenario['expected_risk_score'],
|
||
"actual_risk_score": None,
|
||
"duration_ms": 0,
|
||
"description": scenario['description']
|
||
}
|
||
|
||
def run_all_tests(self) -> Dict:
|
||
"""运行所有测试场景"""
|
||
logger.info("=" * 80)
|
||
logger.info("开始运行收入完整性检测算法测试")
|
||
logger.info("=" * 80)
|
||
|
||
# 加载测试场景
|
||
scenarios = self.load_test_scenarios()
|
||
|
||
# 运行测试
|
||
start_time = time.time()
|
||
for scenario_id, scenario in scenarios.items():
|
||
result = self.run_single_test(scenario_id, scenario)
|
||
self.test_results.append(result)
|
||
time.sleep(0.5) # 防止请求过快
|
||
end_time = time.time()
|
||
|
||
# 生成报告
|
||
report = self.generate_report(end_time - start_time)
|
||
return report
|
||
|
||
def generate_report(self, total_duration: float) -> Dict:
|
||
"""生成测试报告"""
|
||
total_tests = len(self.test_results)
|
||
pass_rate = (self.passed_count / total_tests * 100) if total_tests > 0 else 0
|
||
|
||
report = {
|
||
"test_time": datetime.now().isoformat(),
|
||
"total_tests": total_tests,
|
||
"passed": self.passed_count,
|
||
"failed": self.failed_count,
|
||
"pass_rate": f"{pass_rate:.1f}%",
|
||
"total_duration_seconds": round(total_duration, 2),
|
||
"test_results": self.test_results
|
||
}
|
||
|
||
return report
|
||
|
||
def print_summary(self, report: Dict):
|
||
"""打印测试摘要"""
|
||
print("\n" + "=" * 80)
|
||
print("收入完整性检测算法测试报告")
|
||
print("=" * 80)
|
||
print(f"测试时间: {report['test_time']}")
|
||
print(f"总测试数: {report['total_tests']}")
|
||
print(f"通过: {report['passed']} ✓")
|
||
print(f"失败: {report['failed']} ✗")
|
||
print(f"通过率: {report['pass_rate']}")
|
||
print(f"总耗时: {report['total_duration_seconds']}秒")
|
||
print("=" * 80)
|
||
print()
|
||
|
||
# 按场景打印结果
|
||
for result in report['test_results']:
|
||
status_symbol = "✓" if result['status'] == 'PASSED' else "✗"
|
||
print(f"{status_symbol} {result['scenario_id']}: {result['scenario_name']}")
|
||
print(f" 主播ID: {result['streamer_id']}")
|
||
print(f" 预期等级: {result['expected_risk_level']}")
|
||
print(f" 实际等级: {result['actual_risk_level']}")
|
||
print(f" 预期评分: {result['expected_risk_score']}")
|
||
print(f" 实际评分: {result['actual_risk_score']}")
|
||
|
||
if result['error']:
|
||
print(f" 错误: {result['error']}")
|
||
print()
|
||
|
||
print("=" * 80)
|
||
|
||
def save_report(self, report: Dict):
|
||
"""保存测试报告"""
|
||
output_file = "/Users/liulujian/Documents/code/deeprisk-claude-1/backend/test_data/revenue_test/test_report.json"
|
||
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
json.dump(report, f, ensure_ascii=False, indent=2)
|
||
|
||
logger.info(f"测试报告已保存到: {output_file}")
|
||
print(f"\n📄 详细报告已保存到: {output_file}")
|
||
|
||
def check_api_availability(self) -> bool:
|
||
"""检查API是否可用"""
|
||
try:
|
||
response = requests.get(f"{self.api_base}/risk-detection/execute", timeout=5)
|
||
# 这里可能会返回405 (Method Not Allowed),表示API存在
|
||
# 或者500错误,表示服务正在运行
|
||
if response.status_code in [200, 405, 422, 500]:
|
||
logger.info("✓ API服务可用")
|
||
return True
|
||
else:
|
||
logger.error(f"✗ API服务异常: HTTP {response.status_code}")
|
||
return False
|
||
except Exception as e:
|
||
logger.error(f"✗ 无法连接到API服务: {str(e)}")
|
||
return False
|
||
|
||
def main():
|
||
"""主函数"""
|
||
print("=" * 80)
|
||
print("收入完整性检测算法 - 自动化测试工具")
|
||
print("=" * 80)
|
||
print()
|
||
|
||
# 初始化测试器
|
||
tester = RevenueAlgorithmTester()
|
||
|
||
# 检查API服务
|
||
print("1. 检查API服务...")
|
||
if not tester.check_api_availability():
|
||
print("\n❌ API服务不可用,请确保后端服务正在运行")
|
||
print(" 启动命令: cd /Users/liulujian/Documents/code/deeprisk-claude-1/backend && python app/main.py")
|
||
sys.exit(1)
|
||
|
||
print(" ✓ API服务正常\n")
|
||
|
||
# 运行测试
|
||
print("2. 运行测试场景...")
|
||
report = tester.run_all_tests()
|
||
|
||
# 打印摘要
|
||
print("3. 生成测试报告...")
|
||
tester.print_summary(report)
|
||
|
||
# 保存报告
|
||
tester.save_report(report)
|
||
|
||
# 输出结论
|
||
if tester.failed_count == 0:
|
||
print("🎉 所有测试通过!")
|
||
print("\n✅ 算法实现正确,能够准确识别不同风险等级的收入问题")
|
||
sys.exit(0)
|
||
else:
|
||
print(f"\n⚠️ 有 {tester.failed_count} 个测试失败,请检查算法实现")
|
||
print("\n可能的原因:")
|
||
print(" 1. 阈值配置不正确")
|
||
print(" 2. 数据获取逻辑错误")
|
||
print(" 3. 风险计算公式错误")
|
||
print(" 4. 数据源问题")
|
||
sys.exit(1)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|