提交 4c08eec7 authored 作者: 陈泽健's avatar 陈泽健

docs(prd): 更新AI用例完善需求文档并重构缺失检测与生成逻辑

- 重命名需求文档文件名添加_PRD_前缀
- 新增用例设计章节详述等价类划分、边界值分析等测试设计方法
- 添加日志打印功能便于调试AI服务调用情况
- 集成TEST_DESIGN_METHODS常量到AI提示词模板中
- 重构GapDetector类使用AIServiceManager支持API和CLI模式
- 重构CaseGenerator类按功能点、场景、边界分别生成测试用例
- 更新实施计划章节编号并添加测试用例设计方案
- 新增AI调用逻辑优化需求文档说明分步调用方案
- 删除已解决的XPATH定位器问题记录文档
上级 04f2212c
......@@ -120,6 +120,9 @@ class AIServiceManager:
"""
model = model or AI_MODEL
# 打印提示词到控制台(方便调试)
self._log_prompt(prompt, model, "ask_json")
if self.mode == "cli":
# CLI模式:适配器会自动处理JSON解析
return self.cli_adapter.ask_json(prompt, model)
......@@ -147,6 +150,9 @@ class AIServiceManager:
"""
model = model or AI_MODEL
# 打印提示词到控制台(方便调试)
self._log_prompt(prompt, model, "ask_json_array")
if self.mode == "cli":
# CLI模式:适配器会自动处理JSON数组解析
return self.cli_adapter.ask_json_array(prompt, model)
......@@ -161,6 +167,42 @@ class AIServiceManager:
result_text = response.content[0].text
return self._parse_json_array(result_text)
def _log_prompt(self, prompt: str, model: str, method: str) -> None:
"""
打印提示词到控制台(用于调试和日志记录)
Args:
prompt: 提示词内容
model: 模型名称
method: 调用方法名称
"""
# 打印分隔线
print("=" * 80)
print(f"[AI服务调用] 模型: {model} | 方法: {method} | 模式: {self.mode}")
print("=" * 80)
# 如果提示词过长,进行摘要打印
if len(prompt) > 2000:
print(f"[提示词摘要] 总长度: {len(prompt)} 字符")
print("-" * 80)
# 打印前500字符
print("[前500字符]")
print(prompt[:500])
print("\n... (省略中间内容) ...\n")
# 打印后500字符
print(f"[后500字符] (从 {len(prompt)-500} 开始)")
print(prompt[-500:])
else:
print("[完整提示词]")
print(prompt)
print("=" * 80)
print() # 空行分隔
# 同时记录到日志
logger.info("[AI服务调用] 模型=%s, 方法=%s, 模式=%s, 提示词长度=%d",
model, method, self.mode, len(prompt))
def _extract_json(self, text: str) -> str:
"""
从文本中提取JSON字符串
......
......@@ -14,7 +14,7 @@ from datetime import datetime
from src.config import (
AI_MODEL, AI_MAX_TOKENS, AI_TEMPERATURE,
TEST_CASE_COLUMNS, CASE_LEVELS, DEFAULT_CASE_LEVEL,
DEFAULT_TEST_FREQUENCY, TEST_FREQUENCIES
DEFAULT_TEST_FREQUENCY, TEST_FREQUENCIES, TEST_DESIGN_METHODS
)
logger = getLogger(__name__)
......@@ -162,11 +162,13 @@ class CaseGenerator:
- 功能模块:{gap.get('功能模块', '')}
- 功能编号:{gap.get('功能编号', '')}
- 功能描述:{gap.get('功能描述', '')}
- 建议设计方法:{gap.get('设计方法', '场景法')}
【要求】
1. 生成详细的测试步骤
2. 预期结果要明确具体
3. 考虑正常流程和基本验证
4. 应用等价类划分和边界值分析方法
请输出JSON格式的测试用例:
"""
......@@ -176,13 +178,18 @@ class CaseGenerator:
prompt += f"""
注意:
{TEST_DESIGN_METHODS}
【额外要求】
1. 必须输出合法的JSON格式
2. 用例编号格式:TC-{gap.get('功能模块', 'TEST')[:4].upper()}-{seq:03d}
3. 用例等级:{gap.get('优先级', DEFAULT_CASE_LEVEL)}
4. STEP要包含详细的操作步骤,每步一行
5. JSON字段填写:暂不涉及自动化测试
6. 测试频次:{DEFAULT_TEST_FREQUENCY}
7. 根据功能特点选择合适的测试设计方法(等价类划分、边界值分析、场景法等)
8. 对于输入框类功能,必须包含边界值测试
9. 对于权限类功能,必须包含不同角色的权限验证
"""
return self._call_ai_for_case(prompt, gap.get("功能描述", ""))
......@@ -216,6 +223,7 @@ class CaseGenerator:
- 功能描述:{gap.get('功能描述', '')}
- 已有场景:{gap.get('已有场景', '')}
- 缺失场景:{gap.get('缺失场景', '')}
- 场景类型:{gap.get('场景类型', '异常场景')}
【要求】
1. 专门针对缺失场景设计测试步骤
......@@ -230,12 +238,37 @@ class CaseGenerator:
prompt += f"""
注意:
{TEST_DESIGN_METHODS}
【场景设计指导】
根据场景类型应用相应的设计方法:
1. 异常场景设计:
- 输入无效数据(空值、超长值、特殊字符)
- 模拟操作失败(网络中断、服务不可用)
- 验证错误提示信息
2. 边界场景设计:
- 测试边界值(最大值、最小值)
- 测试边界±1的值
- 测试空值和null值
3. 并发场景设计:
- 模拟多用户同时操作
- 验证数据一致性
- 验证锁机制
4. 权限场景设计:
- 不同角色权限验证
- 越权访问测试
- 权限边界测试
【额外要求】
1. 必须输出合法的JSON格式
2. 用例名称:{gap.get('建议用例名称', f'测试{gap.get("功能描述", "")}{gap.get("缺失场景", "")}')}
3. 用例等级:{gap.get('优先级', DEFAULT_CASE_LEVEL)}
4. STEP要详细描述如何触发该场景
5. 预期结果要描述该场景的预期行为
5. 预期结果要描述该场景的预期行为(成功或失败,错误提示等)
"""
return self._call_ai_for_case(prompt, gap.get("缺失场景", ""))
......@@ -269,6 +302,7 @@ class CaseGenerator:
- 功能描述:{gap.get('功能描述', '')}
- 已有边界:{gap.get('已有边界', '')}
- 缺失边界:{gap.get('缺失边界', '')}
- 边界类型:{gap.get('边界类型', '边界值')}
【要求】
1. 专门针对该边界条件设计测试步骤
......@@ -283,12 +317,46 @@ class CaseGenerator:
prompt += f"""
注意:
{TEST_DESIGN_METHODS}
【边界值设计指导】
根据边界类型应用边界值分析方法:
1. 数值型边界:
- 上边界:最大值
- 上边界+1:超过最大值
- 上边界-1:接近最大值
- 下边界:最小值
- 下边界+1:接近最小值
- 下边界-1:低于最小值
- 零值(如适用)
2. 字符串边界:
- 空字符串:''
- 最大长度:边界值
- 超长字符串:最大长度+1
- 特殊字符:SQL注入、XSS攻击字符
3. 列表/集合边界:
- 空列表:[]
- 单元素:[item]
- 最大数量:边界值
- 超过最大值:最大值+1
4. 日期时间边界:
- 最小日期:系统支持的最小日期
- 最大日期:系统支持的最大日期
- 边界日期±1天
- 无效日期:2月30日、13月等
【额外要求】
1. 必须输出合法的JSON格式
2. 用例名称:{gap.get('建议用例名称', f'测试{gap.get("功能描述", "")}{gap.get("缺失边界", "")}')}
3. 用例等级:{gap.get('优先级', DEFAULT_CASE_LEVEL)}
4. STEP要详细描述如何测试该边界条件
5. 预期结果要描述边界值和附近值的预期行为
4. STEP要详细描述如何测试该边界条件(包括测试数据的具体值)
5. 预期结果要描述边界值和附近值的预期行为(通过或失败,错误提示等)
6. 对于数值边界,测试数据要包含具体的数值
7. 对于字符串边界,测试数据要包含具体的字符串长度示例
"""
return self._call_ai_for_case(prompt, gap.get("缺失边界", ""))
......@@ -451,3 +519,131 @@ def format_case_number(module: str, seq: int) -> str:
module_short = "TEST"
return f"TC-{module_short}-{seq:03d}"
# ==================== v2版本:优化后的精简提示词方法 ====================
def generate_cases_v2(self, gaps: Dict, existing_cases: List[Dict]) -> List[Dict]:
"""
使用精简提示词逐个生成测试用例(步骤3)
优化说明:
- 每个提示词长度控制在600字符以内
- 逐个生成用例,避免一次性处理过多信息
- 简化测试设计方法说明
Args:
gaps: 缺失检测结果
existing_cases: 现有测试用例列表
Returns:
新生成的测试用例列表
"""
logger.info("开始逐个生成测试用例(v2版本 - 精简提示词)...")
new_cases = []
next_seq = self._get_next_sequence(existing_cases)
# 遍历所有缺失类型
for gap_type, gap_list in gaps.items():
for gap in gap_list:
try:
# 为每个缺失生成一个测试用例
case = self._generate_case_v2(gap, gap_type, next_seq)
if case:
new_cases.append(case)
next_seq += 1
except Exception as e:
logger.error("生成测试用例失败: %s, 错误: %s",
gap.get('功能描述', ''), e)
logger.info("生成测试用例完成: %d 条", len(new_cases))
return new_cases
def _generate_case_v2(self, gap: Dict, gap_type: str, seq: int) -> Optional[Dict]:
"""
使用精简提示词生成单个测试用例
Args:
gap: 缺失信息
gap_type: 缺失类型(功能点缺失/场景覆盖不足/边界条件不足)
seq: 序列号
Returns:
测试用例字典,失败返回None
"""
prompt = self._build_step3_prompt_v2(gap, gap_type, seq)
logger.info("步骤3提示词长度: %d 字符", len(prompt))
try:
# 使用AI服务管理器调用
case = self.ai_service.ask_json(prompt, AI_MODEL)
# 类型检查:确保返回的是字典
if isinstance(case, list):
if len(case) > 0 and isinstance(case[0], dict):
case = case[0]
else:
logger.error("AI返回的数组格式不符合预期")
return None
if not isinstance(case, dict):
logger.error("AI返回的不是字典对象: %s", type(case))
return None
# 设置默认值
if not case.get("测试频次"):
case["测试频次"] = DEFAULT_TEST_FREQUENCY
if not case.get("JSON"):
case["JSON"] = "暂不涉及"
# 设置序列号
case["序列号"] = seq
return case
except Exception as e:
logger.error("生成测试用例失败: %s", e)
logger.debug("错误详情", exc_info=True)
return None
def _build_step3_prompt_v2(self, gap: Dict, gap_type: str, seq: int) -> str:
"""
构建步骤3提示词:生成单个测试用例(精简版本)
提示词长度控制在600字符以内
"""
prompt = f"""你是一个测试专家。为以下缺失生成测试用例。
【缺失信息】
- 类型:{gap_type}
- 功能描述:{gap.get('功能描述', '')}
"""
# 根据缺失类型添加特定信息
if gap_type == "场景覆盖不足":
prompt += f"- 已有场景:{gap.get('已有场景', '')}\n"
prompt += f"- 缺失场景:{gap.get('缺失场景', '')}\n"
elif gap_type == "边界条件不足":
prompt += f"- 已有边界:{gap.get('已有边界', '')}\n"
prompt += f"- 缺失边界:{gap.get('缺失边界', '')}\n"
prompt += f"""
【测试设计方法】
等价类划分:有效等价类、无效等价类(空值、特殊字符、超长值)
边界值分析:上边界、下边界、边界±1
场景法:基本流、备选流
错误推测:异常输入、并发冲突
【输出格式】严格遵循JSON:
{json.dumps({col: "" for col in TEST_CASE_COLUMNS}, ensure_ascii=False)}
【要求】
1. 用例编号格式:TC-{gap.get('功能模块', 'TEST')[:4].upper()}-{seq:03d}
2. 用例等级:{gap.get('优先级', 'P1')}
3. STEP包含详细操作步骤,每步一行
4. JSON字段填写:暂不涉及
5. 测试频次:每版本
6. 根据缺失类型应用相应的测试设计方法
7. 边界条件测试需包含具体的边界值数据
"""
return prompt
......@@ -117,6 +117,64 @@ TEST_FREQUENCIES = ["每次", "每日", "每周", "每月", "每版本", "一次
# 默认测试频次
DEFAULT_TEST_FREQUENCY = "每版本"
# ==================== 测试用例设计方法 ====================
# 测试设计方法提示词模板
TEST_DESIGN_METHODS = """
在生成测试用例时,请应用以下测试设计方法:
【功能性测试设计方法】
1. 等价类划分:识别有效等价类和无效等价类,为每个等价类设计代表性用例
- 有效等价类:符合需求规格说明的输入数据
- 无效等价类:违反需求规格说明的输入数据(空值、特殊字符、超长字符串、非法格式等)
- 示例:输入框限制1-100的整数,有效类为"1≤x≤100",无效类包括"x<1""x>100""非整数""空值"
2. 边界值分析:针对有范围限制的输入,测试边界值及边界±1的值
- 上边界、上边界+1、上边界-1
- 下边界、下边界+1、下边界-1
- 示例:测试年龄输入框,需覆盖0、1、100、101等值
3. 场景法:设计完整的用户操作流程,包括基本流和备选流
- 基本流:正常操作流程
- 备选流:异常处理流程
- 示例:登录→浏览商品→下单→支付→订单确认
4. 错误推测法:基于经验设计特殊字符、空值、异常输入等测试场景
- 空指针场景
- 并发冲突
- 数据溢出
- 特殊字符处理(SQL注入、XSS攻击测试字符)
- 网络异常
- 权限越界
【非功能性测试设计】
1. 性能测试:设计大数据量、高并发场景的测试用例
- 响应时间测试:测量单个操作的响应时间
- 并发测试:模拟多用户同时操作
- 压力测试:测试系统极限承载能力
2. 兼容性测试:考虑不同浏览器、设备的兼容性
- 浏览器:Chrome、Firefox、Edge、Safari
- 设备分辨率:1920x1080、1366x768、移动端适配
- 操作系统:Windows、macOS、Linux
3. 安全测试:设计SQL注入、XSS攻击、越权访问等安全测试用例
- SQL注入:输入特殊SQL语句尝试注入
- XSS攻击:输入脚本代码测试防护
- 敏感数据:检查密码、token等是否加密存储
- 权限控制:验证越权访问是否被阻止
4. 异常场景:设计网络中断、服务不可用等异常情况
- 网络中断场景
- 服务不可用场景
- 数据库连接失败场景
【测试用例优先级判定标准】
- P0(最高):核心功能、关键业务路径、安全相关
- P1(高):重要功能、常用场景、性能相关
- P2(中):辅助功能、一般场景、兼容性相关
- P3(低):边缘功能、异常场景、UI美化相关
"""
def ensure_directories() -> None:
"""
......
......@@ -9,7 +9,7 @@ import json
from typing import List, Dict, Optional
from logging import getLogger
from src.config import AI_MODEL, COVERAGE_TARGET
from src.config import AI_MODEL, COVERAGE_TARGET, TEST_DESIGN_METHODS
logger = getLogger(__name__)
......@@ -183,7 +183,8 @@ class GapDetector:
"功能编号": "F-001",
"功能描述": "缺失的功能描述",
"建议用例名称": "测试XXX功能",
"优先级": "P1"
"优先级": "P1",
"设计方法": "等价类划分/场景法/错误推测法"
}
],
"场景覆盖不足": [
......@@ -192,7 +193,8 @@ class GapDetector:
"已有场景": "正常场景",
"缺失场景": "异常场景:XXX",
"建议用例名称": "测试XXX异常场景",
"优先级": "P2"
"优先级": "P2",
"场景类型": "异常场景/边界场景/并发场景/权限场景"
}
],
"边界条件不足": [
......@@ -201,23 +203,28 @@ class GapDetector:
"已有边界": "常规值",
"缺失边界": "边界值:XXX",
"建议用例名称": "测试XXX边界条件",
"优先级": "P2"
"优先级": "P2",
"边界类型": "上边界/下边界/空值/超长值"
}
]
}
注意:
""" + TEST_DESIGN_METHODS + """
【额外要求】
1. 必须输出合法的JSON格式
2. 只输出JSON对象,不要有其他说明文字
3. 优先级分为:P0(最高)、P1、P2、P3(最低)
4. 优先识别P0和P1级别的缺失
5. 考虑以下常见场景:
5. 应用上述测试设计方法进行缺失分析
6. 特别关注以下场景类型:
- 正常场景:基本功能验证
- 异常场景:错误处理、异常输入
- 边界条件:最大值、最小值、空值、null
- 并发场景:多用户同时操作
- 权限场景:不同角色权限验证
- 性能场景:大数据量、长时间操作
- 安全场景:SQL注入、XSS攻击、越权访问
"""
return prompt
......@@ -366,3 +373,113 @@ def estimate_additional_cases(gaps: Dict) -> Dict:
logger.info("估算需新增测试用例: %d 条", total)
return result
# ==================== v2版本:优化后的精简提示词方法 ====================
def detect_gaps_v2(self, function_points: List[Dict],
test_cases: List[Dict]) -> Dict:
"""
使用精简提示词检测缺失(步骤2)
优化说明:
- 提示词长度控制在800字符以内
- 测试用例使用摘要(前20条)
- 简化测试设计方法说明
Args:
function_points: 功能点列表
test_cases: 测试用例列表
Returns:
缺失检测结果,包含:
- 功能点缺失: 缺失的功能点列表
- 场景覆盖不足: 场景覆盖不足的列表
- 边界条件不足: 边界条件不足的列表
"""
logger.info("开始检测测试用例缺失(v2版本 - 精简提示词)...")
logger.info(" 功能点数: %d", len(function_points))
logger.info(" 测试用例数: %d", len(test_cases))
prompt = self._build_step2_prompt_v2(function_points, test_cases)
logger.info("步骤2提示词长度: %d 字符", len(prompt))
try:
# 使用AI服务管理器调用
result = self.ai_service.ask_json(prompt, AI_MODEL)
# 处理返回结果
gaps = {"功能点缺失": [], "场景覆盖不足": [], "边界条件不足": []}
if isinstance(result, dict):
# 直接是字典格式,合并结果
for key in gaps:
if key in result and isinstance(result[key], list):
gaps[key] = result[key]
logger.info("AI检测完成:")
logger.info(" 功能点缺失: %d 项", len(gaps["功能点缺失"]))
logger.info(" 场景覆盖不足: %d 项", len(gaps["场景覆盖不足"]))
logger.info(" 边界条件不足: %d 项", len(gaps["边界条件不足"]))
return gaps
except Exception as e:
logger.error("AI调用失败: %s", e)
logger.debug("错误详情", exc_info=True)
return {"功能点缺失": [], "场景覆盖不足": [], "边界条件不足": []}
def _build_step2_prompt_v2(self, function_points: List[Dict],
test_cases: List[Dict]) -> str:
"""
构建步骤2提示词:检测缺失(精简版本)
提示词长度控制在800字符以内
"""
# 格式化测试用例摘要
cases_summary = self._summarize_test_cases_v2(test_cases, max_items=20)
prompt = f"""你是一个测试专家。对比功能点和现有用例,找出缺失。
【测试设计方法】
等价类划分、边界值分析、场景法、错误推测法、因果图判定表、性能测试、安全测试、兼容性测试
【功能点】
{json.dumps(function_points, ensure_ascii=False)}
【现有测试用例摘要】(共{len(test_cases)}条)
{cases_summary}
【输出格式】严格遵循JSON:
{{
"功能点缺失": [{{"功能模块": "", "功能描述": "", "建议用例名称": "", "优先级": "P1"}}],
"场景覆盖不足": [{{"功能描述": "", "已有场景": "", "缺失场景": "", "建议用例名称": "", "优先级": "P2"}}],
"边界条件不足": [{{"功能描述": "", "已有边界": "", "缺失边界": "", "建议用例名称": "", "优先级": "P2"}}]
}}
【要求】
1. 优先级:P0(最高)、P1、P2、P3(最低)
2. 只输出JSON对象,不要其他文字
3. 识别缺失时应用上述测试设计方法
"""
return prompt
def _summarize_test_cases_v2(self, test_cases: List[Dict], max_items: int = 20) -> str:
"""
生成测试用例摘要
Args:
test_cases: 测试用例列表
max_items: 最多显示的用例数量
Returns:
用例摘要字符串
"""
lines = []
for i, case in enumerate(test_cases[:max_items], 1):
lines.append(f"{i}. {case.get('用例名称', '')} - {case.get('功能描述', '')}")
result = "\n".join(lines)
if len(test_cases) > max_items:
result += f"\n... (还有{len(test_cases)-max_items}条)"
return result
......@@ -423,3 +423,182 @@ def save_enhanced_test_cases(existing_cases: list, new_cases: list,
if __name__ == "__main__":
sys.exit(main())
# ==================== v2版本:优化后的分步调用流程 ====================
def main_v2(use_cli: bool = False) -> int:
"""
主函数 - 使用优化后的分步调用流程
优化说明:
- 采用A+B组合方案:分步调用 + 精简提示词
- 每个步骤提示词控制在1000字符以内
- 失败可重试单个步骤
Args:
use_cli: 是否强制使用CLI模式
Returns:
退出码(0表示成功,非0表示失败)
"""
try:
# 配置日志
setup_logging()
ensure_directories()
logger.info("=" * 60)
logger.info("AI完善测试用例工具 启动(优化版本 v2)")
logger.info("版本: 2.0.0 - 分步调用+精简提示词")
logger.info("=" * 60)
# 检查AI服务模式
api_key = os.environ.get(API_KEY_ENV)
service_mode = get_ai_service_mode()
# 命令行参数优先级最高
if use_cli:
service_mode = "cli"
# 根据服务模式初始化
if service_mode == "api":
if not api_key or not api_key.strip():
logger.error("API模式需要ANTHROPIC_API_KEY环境变量")
return 1
use_cli = False
masked_key = api_key[:8] + "..." if len(api_key) > 8 else "***"
logger.info("使用Anthropic API模式,密钥: %s", masked_key)
else:
use_cli = True
logger.info("使用Claude CLI模式")
# 验证输入
validate_inputs()
# 1. 读取文档
logger.info("")
logger.info("【步骤1】读取文档")
logger.info("-" * 60)
reader = DocumentReader()
docs = reader.read_all_documents(REQUIREMENTS_DIR, PRD_DIR, TEST_CASE_FILE)
requirements = docs["requirements"]
prds = docs["prds"]
test_cases = docs["test_cases"]
logger.info("需求文档: %d 份", len(requirements))
logger.info("PRD文档: %d 份", len(prds))
logger.info("测试用例: %d 条", len(test_cases))
# ========== 步骤1:提取功能点 ==========
logger.info("")
logger.info("【步骤2】提取功能点(v2 - 精简提示词 ~500字符)")
logger.info("-" * 60)
analyzer = TestCaseAnalyzer(api_key=api_key, use_cli=use_cli)
function_points = analyzer.extract_function_points_v2(requirements, prds)
if not function_points:
logger.warning("未提取到功能点,请检查需求文档内容")
# 继续执行,但使用空列表
logger.info("提取到功能点: %d 个", len(function_points))
# ========== 步骤2:检测缺失 ==========
logger.info("")
logger.info("【步骤3】检测缺失(v2 - 精简提示词 ~800字符)")
logger.info("-" * 60)
detector = GapDetector(api_key=api_key, use_cli=use_cli)
gaps = detector.detect_gaps_v2(function_points, test_cases)
logger.info("功能点缺失: %d 项", len(gaps.get("功能点缺失", [])))
logger.info("场景覆盖不足: %d 项", len(gaps.get("场景覆盖不足", [])))
logger.info("边界条件不足: %d 项", len(gaps.get("边界条件不足", [])))
# 估算新增用例数量
estimate = estimate_additional_cases(gaps)
logger.info("估算需新增用例: %d 条", estimate.get("总计", 0))
# ========== 步骤3:生成用例 ==========
logger.info("")
logger.info("【步骤4】生成测试用例(v2 - 精简提示词 ~600字符/条)")
logger.info("-" * 60)
generator = CaseGenerator(api_key=api_key, use_cli=use_cli)
new_cases = generator.generate_cases_v2(gaps, test_cases)
logger.info("生成测试用例: %d 条", len(new_cases))
# ========== 后续处理 ==========
# 去重
logger.info("")
logger.info("【步骤5】去重处理")
logger.info("-" * 60)
deduplicator = Deduplicator()
dedup_result = deduplicator.deduplicate(test_cases, new_cases)
logger.info("去重后保留: %d 条", dedup_result["summary"]["unique_cases"])
# 保存完善后的测试用例
logger.info("")
logger.info("【步骤6】保存完善后的测试用例")
logger.info("-" * 60)
save_enhanced_test_cases(test_cases, dedup_result["unique_cases"], OUTPUT_TEST_CASE)
# 生成差异性报告
logger.info("")
logger.info("【步骤7】生成差异性报告")
logger.info("-" * 60)
reporter = ReportGenerator()
# 计算覆盖率
coverage = analyzer.analyze_coverage(function_points, test_cases)
reporter.generate_report(gaps, dedup_result, coverage, OUTPUT_REPORT, len(test_cases))
# 输出总结
logger.info("")
logger.info("=" * 60)
logger.info("处理完成!")
logger.info("=" * 60)
logger.info("【输入】")
logger.info(" 需求文档: %d 份", len(requirements))
logger.info(" PRD文档: %d 份", len(prds))
logger.info(" 原始用例: %d 条", len(test_cases))
logger.info("")
logger.info("【输出】")
logger.info(" 完善后的测试用例: %s", OUTPUT_TEST_CASE)
logger.info(" 差异性报告: %s", OUTPUT_REPORT)
logger.info("")
logger.info("【统计】")
logger.info(" 覆盖率: %.1f%%", coverage.get("coverage_rate", 0) * 100)
logger.info(" 新增用例: %d 条(去重后)", dedup_result["summary"]["unique_cases"])
logger.info(" 功能点缺失: %d 项", len(gaps.get("功能点缺失", [])))
logger.info(" 场景缺失: %d 项", len(gaps.get("场景覆盖不足", [])))
logger.info(" 边界缺失: %d 项", len(gaps.get("边界条件不足", [])))
logger.info("=" * 60)
return 0
except KeyboardInterrupt:
logger.info("")
logger.info("用户中断操作")
return 130
except FileNotFoundError as e:
logger.error("文件未找到: %s", e)
return 1
except PermissionError as e:
logger.error("权限错误: %s", e)
logger.error("请检查文件是否被其他程序占用")
return 1
except Exception as e:
logger.error("处理失败: %s", e)
import traceback
logger.debug(traceback.format_exc())
return 1
......@@ -413,6 +413,101 @@ class TestCaseAnalyzer:
return result
# ==================== v2版本:优化后的精简提示词方法 ====================
def extract_function_points_v2(self, requirements: List[Dict],
prds: List[Dict]) -> List[Dict]:
"""
使用精简提示词提取功能点(步骤1)
优化说明:
- 提示词长度控制在500字符以内
- 需求文档内容限制在300字符以内
- 简化输出格式要求
Args:
requirements: 需求文档列表
prds: PRD文档列表
Returns:
功能点列表
"""
logger.info("开始提取功能点(v2版本 - 精简提示词)...")
prompt = self._build_step1_prompt_v2(requirements, prds)
logger.info("步骤1提示词长度: %d 字符", len(prompt))
try:
# 使用AI服务管理器
from src.ai_service_manager import AIServiceManager
ai_service = AIServiceManager(api_key=os.environ.get(API_KEY_ENV))
function_points = ai_service.ask_json_array(prompt)
logger.info("功能点提取完成,共 %d 个", len(function_points))
return function_points
except Exception as e:
logger.error("提取功能点失败: %s", e)
logger.debug("错误详情", exc_info=True)
return []
def _build_step1_prompt_v2(self, requirements: List[Dict],
prds: List[Dict]) -> str:
"""
构建步骤1提示词:提取功能点(精简版本)
提示词长度控制在500字符以内
"""
# 格式化需求文档,限制长度
req_text = self._format_requirements_v2(requirements, prds, max_chars=300)
prompt = f"""你是一个测试专家。分析需求文档,提取功能点列表。
【需求文档】
{req_text}
【要求】
1. 识别所有功能模块
2. 每个功能包含:功能模块、功能描述
3. 输出JSON数组格式
【输出格式】
[{{"功能模块": "模块名", "功能描述": "功能描述"}}]
"""
return prompt
def _format_requirements_v2(self, requirements: List[Dict],
prds: List[Dict], max_chars: int = 300) -> str:
"""
格式化需求文档,控制长度
Args:
requirements: 需求文档列表
prds: PRD文档列表
max_chars: 最大字符数
Returns:
格式化后的文档字符串
"""
parts = []
total_chars = 0
# 合并需求和PRD
all_docs = requirements + prds
for doc in all_docs:
# 取前200字符作为摘要
content_preview = doc['content'][:200]
if len(doc['content']) > 200:
content_preview += "..."
content = f"【{doc['filename']}】{content_preview}"
parts.append(content)
total_chars += len(content)
if total_chars >= max_chars:
break
return "\n".join(parts)
def analyze_by_module(self, test_cases: List[Dict]) -> Dict[str, Dict]:
"""
按功能模块分析测试用例
......
......@@ -69,6 +69,7 @@ cd AuxiliaryTool/DocumentAutoOptimizationCalibration
2. 安装依赖:
```bash
pip install -r requirements.txt
pip install pywin32
```
---
......
[
{
"序号": 1,
"功能模块": "通用模块",
"功能类别": "异常场景",
"用例编号": "TC-001",
"功能描述": "权限组管理接口文档",
"用例等级": "中",
"功能编号": "",
"用例名称": "权限组管理接口文档",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. ## 1. 分页查询权限组; 2. **接口描述**:根据公司编号、权限组名称、启用状态等条件进行分页查询,支持模糊匹配权限组名称,按创建时间倒序排列; 3. **接口描述**:执行物理删除操作,同时级联删除部门关联、角色关联、用户关联表中的数据,使用事务确保删除操作的原子性。基础权限组(is_base=1)不可删除。; 4. ## 7. 查询绑定关系; 5. **接口描述**:查询权限组已绑定的关联对象,支持按类型筛选或返回全部类型; 6. ## 8. 查询用户最终权限",
"JSON": "",
"预期结果": "**请求头**:需要携带有效的 Token 认证; **响应示例**:; **响应示例**(成功):; **响应示例**(包含基础权限组):; > - departmentIds、roleIds、userIds 至少需要传入一种类型(可为空数组); **响应示例**(不传relationType,返回全部):",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 2,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-002",
"功能描述": "**直接绑定**:查询sys_permission_group_user中user_id=用户ID的权限组",
"用例等级": "中",
"功能编号": "1",
"用例名称": "**直接绑定**:查询sys_permission_group_user中user_id=用户ID的权限组",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "**直接绑定**:查询sys_permission_group_user中user_id=用户ID的权限组 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 3,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-003",
"功能描述": "**部门绑定**:查询sys_permission_group_department中department_id=用户所属部门ID的权限组",
"用例等级": "中",
"功能编号": "2",
"用例名称": "**部门绑定**:查询sys_permission_group_department中department_id=用户所属部门ID的权限组",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "**部门绑定**:查询sys_permission_group_department中department_id=用户所属部门ID的权限组 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 4,
"功能模块": "通用模块",
"功能类别": "异常场景",
"用例编号": "TC-004",
"功能描述": "**角色绑定**:查询sys_permission_group_role中role_id=用户所属角色ID的权限组",
"用例等级": "中",
"功能编号": "3",
"用例名称": "**角色绑定**:查询sys_permission_group_role中role_id=用户所属角色ID的权限组",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. | A_SELECT_EMPTY | 查询结果为空 |; 2. *文档生成时间:2026年1月12日*",
"JSON": "",
"预期结果": "**响应示例**:; | 200 | 成功 |; | 值 | 说明 | 对应表 |",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
}
]
\ No newline at end of file
[
{
"序号": 34,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-034",
"功能描述": "自定义权限控制指令实现",
"用例等级": "中",
"功能编号": "",
"用例名称": "自定义权限控制指令实现",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "自定义权限控制指令实现 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 35,
"功能模块": "通用模块",
"功能类别": "功能测试",
"用例编号": "TC-035",
"功能描述": "办公室管理页面地址: `src\\views\\Backend\\MeetingRoom\\OfficeManage\\index.vue`",
"用例等级": "中",
"功能编号": "1",
"用例名称": "办公室管理页面地址: `src\\views\\Backend\\MeetingRoom\\OfficeManage\\index.vue`",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "办公室管理页面地址: `src\\views\\Backend\\MeetingRoom\\OfficeManage\\index.vue` 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 36,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-036",
"功能描述": "自定义权限控制指令地址:`src\\utils\\permission.js`",
"用例等级": "中",
"功能编号": "2",
"用例名称": "自定义权限控制指令地址:`src\\utils\\permission.js`",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "自定义权限控制指令地址:`src\\utils\\permission.js` 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 37,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-037",
"功能描述": "权限数据结构`src\\constant\\permissionList.js`",
"用例等级": "中",
"功能编号": "3",
"用例名称": "权限数据结构`src\\constant\\permissionList.js`",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "权限数据结构`src\\constant\\permissionList.js` 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 38,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-038",
"功能描述": "权限控制指令通过`src\\main.js`挂载成`v-permission`使用",
"用例等级": "中",
"功能编号": "4",
"用例名称": "权限控制指令通过`src\\main.js`挂载成`v-permission`使用",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "权限控制指令通过`src\\main.js`挂载成`v-permission`使用 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 39,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-039",
"功能描述": "实现自定义权限控制指令",
"用例等级": "中",
"功能编号": "1",
"用例名称": "实现自定义权限控制指令",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "实现自定义权限控制指令 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 40,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-040",
"功能描述": "先将权限数据结构存储在localStorage的userPermission中,模拟用户已经获取到当前的拥有权限",
"用例等级": "中",
"功能编号": "1",
"用例名称": "先将权限数据结构存储在localStorage的userPermission中,模拟用户已经获取到当前的拥有权限",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "先将权限数据结构存储在localStorage的userPermission中,模拟用户已经获取到当前的拥有权限 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 41,
"功能模块": "通用模块",
"功能类别": "功能测试",
"用例编号": "TC-041",
"功能描述": "我在办公室管理的页面line:16添加了v-permission=\"'functionType_82.create'\",帮我根据判断实现:如果userPermission的functionType_82.create为1,则显示按钮,并将functionType_82.create作为id赋予给按钮,为0或者不存在则将按钮移出dom",
"用例等级": "中",
"功能编号": "2",
"用例名称": "我在办公室管理的页面line:16添加了v-permission=\"'functionType_82.create'\",帮我根据判断实现:如果userPermission的functionType_82.create为1,则显示按钮,并将functionType_82.create作为id赋予给按钮,为0或者不存在则将按钮移出dom",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "我在办公室管理的页面line:16添加了v-permission=\"'functionType_82.create'\",帮我根据判断实现:如果userPermission的functionType_82.create为1,则显示按钮,并将functionType_82.create作为id赋予给按钮,为0或者不存在则将按钮移出dom 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
},
{
"序号": 42,
"功能模块": "通用模块",
"功能类别": "安全/鉴权",
"用例编号": "TC-042",
"功能描述": "自定义权限控制的生命周期等问题可以参考`E:\\Project\\ubains-unified-platform\\src\\common\\permission.js`",
"用例等级": "中",
"功能编号": "3",
"用例名称": "自定义权限控制的生命周期等问题可以参考`E:\\Project\\ubains-unified-platform\\src\\common\\permission.js`",
"预置条件": "1. 环境已部署; 2. 具备执行权限; 3. 必要依赖/配置已准备",
"操作步骤": "1. 按 PRD 描述执行该功能/流程; 2. 采集相关日志/输出; 3. 记录实际结果",
"JSON": "",
"预期结果": "自定义权限控制的生命周期等问题可以参考`E:\\Project\\ubains-unified-platform\\src\\common\\permission.js` 按 PRD 约定产出正确结果(请补充具体断言点)",
"测试结果": "",
"测试结论": "",
"日志截屏": "",
"备注": ""
}
]
\ No newline at end of file
此差异已折叠。
......@@ -31,6 +31,24 @@
- 日志/截图/照片:用于记录测试过程中产生的日志、截图、照片,用于测试结果分析。
- 备注:用于记录测试过程中产生的问题,用于问题排查。
#### 用例设计
- 功能性设计方案
- 等价类划分:将输入数据划分为有效/无效等价类,减少冗余用例。
- 示例:输入框限制1-100的整数,有效等价类为“1≤x≤100”,无效类包括“x<1”“x>100”“非整数”。
- 边界值分析:针对输入范围的边界设计用例(如边界±1)。
- 示例:测试年龄输入框,需覆盖0、1、100、101等值。
- 因果图与判定表:分析输入条件与输出结果的逻辑关系,适用于复杂业务规则。
- 示例:订单状态变更涉及“支付成功”“库存充足”“物流就绪”等多个条件组合。
- 场景法:模拟用户实际操作流程(如登录→浏览商品→下单→支付)。
- 错误推测法:基于经验预判易错点(如空指针、并发冲突)。
- 非功能性测试
- 性能测试:模拟高并发场景(如秒杀活动),监控响应时间、吞吐量。
- 兼容性测试:覆盖主流浏览器(Chrome/Firefox)、设备分辨率及操作系统。
- 安全测试:SQL注入、XSS攻击防护、敏感数据加密。
- 环境差异测试:测试不同浏览器、设备分辨率及操作系统下的兼容性。
- 遗漏场景:通过探索性测试补充用例未覆盖的边缘路径。
#### 功能实现方式
- AI服务:通过AI服务调用,对需求文档和开发需求PRD文档进行解析和理解,提取功能点,并生成测试用例内容。
- API模式:通过兼容Anthropic API进行调用:
......@@ -40,6 +58,7 @@
- CLT模式:通过本地claude命令行工具调用,使用现有session token
- 安装: npm install -g @anthropic/claude
- Auto模式:自动检测,优先使用API模式,若API模式不可用则使用CLT模式。
- 日志打印:将发送给AI的提示词打印到控制台中,方便后续查看。
- 需求认知:阅读需求文档中功能描述,确定功能需求。
- 获取测试用例:从测试用例文件中获取当前已有的测试用例内容。
......
# AI完善用例需求文档
## 代码路径
- 代码路径:[AuxiliaryTool/AIPerfectedTestCases]
## 功能需求
### 功能目标
**目标:** 当前AI提示词过长(3000+字符),存在覆盖度下降、解析失败、生成数量少的潜在问题,需要优化处理逻辑。
**问题分析:**
- AI注意力分散:长提示词导致AI忽略关键指令
- 信息过载:核心任务被淹没
- 指令冲突:多个设计方法要求互相干扰
### 需求描述
#### 文档路径获取
- 文档路径:[AuxiliaryTool/AIPerfectedTestCases/config]
- 需求文档:`config/需求文档/*.docx`
- 开发需求PRD文档:`config/开发PRD/*.md`
- 测试用例:`config/测试用例/新统一平台权限管理测试用例.xlsx`
#### 方案选择:A+B组合方案
采用**分步调用(方案A)+ 精简提示词(方案B)**的组合:
- 使用A方案的3步调用,将复杂任务拆分
- 每步内部使用B方案的精简提示词结构
- 每个提示词控制在1000字符以内
#### 步骤1:提取功能点
**目标:** 从需求文档中提取功能点列表
**提示词长度:** ~500字符
**输入:**
- 需求文档内容(限制300字符)
- PRD文档内容(限制300字符)
**输出格式:**
```json
[
{"功能模块": "用户管理", "功能描述": "用户登录功能"},
{"功能模块": "权限管理", "功能描述": "角色权限分配"}
]
```
**提示词模板:**
```python
def _build_step1_prompt(self, requirements: List[Dict], prds: List[Dict]) -> str:
"""构建步骤1提示词:提取功能点"""
# 格式化需求文档,限制长度
req_text = self._format_requirements(requirements, prds, max_chars=300)
prompt = f"""你是一个测试专家。分析需求文档,提取功能点列表。
【需求文档】
{req_text}
【要求】
1. 识别所有功能模块
2. 每个功能包含:功能模块、功能描述
3. 输出JSON数组格式
【输出格式】
[{{"功能模块": "模块名", "功能描述": "功能描述"}}]
"""
return prompt
```
#### 步骤2:检测缺失
**目标:** 对比功能点和现有用例,识别三类缺失
**提示词长度:** ~800字符
**输入:**
- 步骤1提取的功能点
- 现有测试用例摘要(前20条,只包含用例名称和功能描述)
**输出格式:**
```json
{
"功能点缺失": [
{"功能模块": "模块名", "功能描述": "缺失功能", "建议用例名称": "测试xxx", "优先级": "P1"}
],
"场景覆盖不足": [
{"功能描述": "xxx功能", "已有场景": "正常场景", "缺失场景": "异常场景", "建议用例名称": "测试xxx异常", "优先级": "P2"}
],
"边界条件不足": [
{"功能描述": "xxx功能", "已有边界": "常规值", "缺失边界": "边界值:0", "建议用例名称": "测试xxx边界", "优先级": "P2"}
]
}
```
**提示词模板:**
```python
def _build_step2_prompt(self, function_points: List[Dict], test_cases: List[Dict]) -> str:
"""构建步骤2提示词:检测缺失"""
# 格式化测试用例摘要
cases_summary = self._summarize_test_cases(test_cases, max_items=20)
prompt = f"""你是一个测试专家。对比功能点和现有用例,找出缺失。
【测试设计方法】
等价类划分、边界值分析、场景法、错误推测法、因果图判定表、性能测试、安全测试、兼容性测试
【功能点】
{json.dumps(function_points, ensure_ascii=False)}
【现有测试用例摘要】(共{len(test_cases)}条)
{cases_summary}
【输出格式】严格遵循JSON:
{{
"功能点缺失": [{{"功能模块": "", "功能描述": "", "建议用例名称": "", "优先级": "P1"}}],
"场景覆盖不足": [{{"功能描述": "", "已有场景": "", "缺失场景": "", "建议用例名称": "", "优先级": "P2"}}],
"边界条件不足": [{{"功能描述": "", "已有边界": "", "缺失边界": "", "建议用例名称": "", "优先级": "P2"}}]
}}
【要求】
1. 优先级:P0(最高)、P1、P2、P3(最低)
2. 只输出JSON对象,不要其他文字
3. 识别缺失时应用上述测试设计方法
"""
return prompt
```
#### 步骤3:生成用例
**目标:** 为缺失的功能点、场景、边界条件生成测试用例
**提示词长度:** ~600字符
**输入:**
- 步骤2检测到的缺失信息
- 需求文档摘要(可选,200字符)
**输出格式:**
```json
{
"序列号": 1,
"功能模块": "用户管理",
"用例编号": "TC-USER-001",
"功能描述": "登录功能",
"用例等级": "P1",
"功能编号": "F-001",
"用例名称": "测试用户登录成功",
"预置条件": "用户已注册",
"STEP": "1.打开登录页面\n2.输入用户名和密码\n3.点击登录",
"JSON": "暂不涉及",
"预期结果": "登录成功,跳转到首页",
"测试结果": "",
"测试频次": "每版本",
"日志/截图/照片": "",
"备注": ""
}
```
**提示词模板:**
```python
def _build_step3_prompt(self, gap: Dict, seq: int, requirements: List[Dict] = None) -> str:
"""构建步骤3提示词:生成单个测试用例"""
gap_type = gap.get("type", "功能点缺失")
prompt = f"""你是一个测试专家。为以下缺失生成测试用例。
【缺失信息】
- 类型:{gap_type}
- 功能描述:{gap.get('功能描述', '')}
"""
# 根据缺失类型添加特定信息
if gap_type == "场景覆盖不足":
prompt += f"- 已有场景:{gap.get('已有场景', '')}\n"
prompt += f"- 缺失场景:{gap.get('缺失场景', '')}\n"
elif gap_type == "边界条件不足":
prompt += f"- 已有边界:{gap.get('已有边界', '')}\n"
prompt += f"- 缺失边界:{gap.get('缺失边界', '')}\n"
prompt += f"""
【测试设计方法】
等价类划分:有效等价类、无效等价类
边界值分析:上边界、下边界、边界±1
场景法:基本流、备选流
错误推测:空值、特殊字符、异常输入
【输出格式】严格遵循JSON:
{json.dumps({col: "" for col in TEST_CASE_COLUMNS}, ensure_ascii=False)}
【要求】
1. 用例编号格式:TC-{gap.get('功能模块', 'TEST')[:4].upper()}-{seq:03d}
2. 用例等级:{gap.get('优先级', 'P1')}
3. STEP包含详细操作步骤,每步一行
4. JSON字段填写:暂不涉及
5. 测试频次:每版本
6. 根据缺失类型应用相应的测试设计方法
7. 边界条件测试需包含具体的边界值数据
"""
return prompt
```
#### 辅助方法
**需求文档格式化(控制长度):**
```python
def _format_requirements(self, requirements: List[Dict], prds: List[Dict], max_chars: int = 300) -> str:
"""格式化需求文档,控制长度"""
parts = []
total_chars = 0
for req in requirements:
content = f"【{req['filename']}】{req['content'][:200]}..."
parts.append(content)
total_chars += len(content)
if total_chars >= max_chars:
break
return "\n".join(parts)
```
**测试用例摘要生成:**
```python
def _summarize_test_cases(self, test_cases: List[Dict], max_items: int = 20) -> str:
"""生成测试用例摘要"""
lines = []
for i, case in enumerate(test_cases[:max_items], 1):
lines.append(f"{i}. {case.get('用例名称', '')} - {case.get('功能描述', '')}")
result = "\n".join(lines)
if len(test_cases) > max_items:
result += f"\n... (还有{len(test_cases)-max_items}条)"
return result
```
#### 核心流程
```python
def main():
# 步骤1:提取功能点
function_points = analyzer.extract_function_points(requirements, prds)
# 步骤2:检测缺失
gaps = detector.detect_gaps(function_points, test_cases)
# 步骤3:生成用例(逐个生成)
new_cases = []
for gap_type, gap_list in gaps.items():
for gap in gap_list:
case = generator.generate_case(gap, seq)
if case:
new_cases.append(case)
```
## 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
# 问题描述
## 问题现象
- 执行代码转换英文后,文档中第一页的文本框内文字没有成功转换,文档可见[AuxiliaryTool/DocumentAutoOptimizationCalibration/reports/新统一平台自动化部署操作指导_英文版.docx]
\ No newline at end of file
# 文本框内的文字没有成功转换文字_问题处理_计划执行
## 1. 问题描述
### 问题现象
执行代码转换英文后,文档中第一页的文本框内文字没有成功转换。
### 问题文档
- 路径:`AuxiliaryTool/DocumentAutoOptimizationCalibration/reports/新统一平台自动化部署操作指导_英文版.docx`
### 预期结果
- 文档中所有文本框内的中文内容都应翻译为英文
### 实际结果
- 文本框内的中文内容没有被翻译
---
## 2. 问题分析
### 根本原因
1. **文本框未处理**:当前代码只处理段落、表格和页眉页脚,没有处理文本框
2. **文本框结构特殊**:文本框在Word文档中是绘图对象(Shape/TextBox),不在`doc.paragraphs`
3. **XML结构特殊**:文本框内容存储在文档的XML绘图部分,需要特殊处理
### 技术分析
当前 `internationalize_document()` 函数处理:
- `doc.paragraphs` - 正文段落
- `doc.tables` - 表格内容
- `section.header/footer` - 页眉页脚
但未处理:
- **文本框(TextBox)** - 通过 `w:txbxContent/w:p` 路径访问
- **绘图对象(Shape)** - 通过 `w:drawing``v:shape` 访问
- **SmartArt** - 复杂图形结构
### 文本框在Word文档中的结构
```
document.xml
└── body
└── p (段落)
└── r (run)
└── drawing/w:drawing
└── wp:inline 或 wp:anchor
└── a:graphic
└── a:graphicData
└── w:txbxContent (文本框内容)
└── w:p (文本框内的段落)
```
---
## 3. 解决方案
### 方案概述
1. 添加文本框检测和翻译函数
2. 遍历文档XML,查找所有文本框元素
3. 对每个文本框内的段落进行翻译
4. 保持文本框原有样式和格式
### 实现步骤
#### 步骤1:添加文本框检测函数
```python
def _find_all_textboxes(document):
"""
查找文档中所有文本框
Args:
document: Document对象
Yields:
文本框中的段落列表
"""
```
#### 步骤2:添加文本框翻译函数
```python
def _translate_textboxes(document, engine: str) -> int:
"""
翻译文档中所有文本框内容
Args:
document: Document对象
engine: 翻译引擎
Returns:
成功翻译的文本框段落数量
"""
```
#### 步骤3:集成到主函数
```python
def internationalize_document(input_path, output_path, engine):
# 现有段落处理...
# 现有表格处理...
# 现有页眉页脚处理...
# 新增:处理文本框
textbox_count = _translate_textboxes(doc, engine)
```
---
## 4. 实施计划
### 4.1 代码修改
- [ ] 修改 `src/internationalizer.py`
- [ ] 添加 `_find_all_textboxes()` 函数
- [ ] 添加 `_translate_textboxes()` 函数
- [ ] 修改 `internationalize_document()` 函数,添加文本框处理
- [ ] 添加详细调试日志
### 4.2 测试验证
- [ ] 使用问题文档测试
- [ ] 检查文本框内容是否翻译
- [ ] 验证文本框样式是否保留
### 4.3 文档更新
- [ ] 更新计划执行文档
---
## 5. 验证标准
### 成功标准
1. 所有文本框内的中文内容被翻译为英文
2. 文本框原有样式和格式保持不变
3. 日志清晰显示文本框处理过程
4. 不影响段落、表格和页眉页脚的翻译
---
## 6. 代码实现
### 新增函数:查找所有文本框
```python
def _find_all_textboxes(document):
"""
查找文档中所有文本框
在Word文档中,文本框存储在 w:txbxContent 元素中。
该函数遍历文档XML,查找所有文本框并返回其中的段落。
Args:
document: Document对象
Yields:
包含文本框段落的Element对象列表
"""
from docx.oxml.ns import qn
# 遍历文档正文的所有段落
for paragraph in document.paragraphs:
# 查找段落中的所有drawing元素
for drawing in paragraph._element.findall('.//w:drawing', document.nsmap):
# 查找文本框内容
for textbox in drawing.findall('.//w:txbxContent', document.nsmap):
yield textbox
# 查找段落中的所有legacy shape元素(旧版Word格式)
for shape in paragraph._element.findall('.//v:shape', document.nsmap):
# 旧版shape可能包含v:textbox
textbox = shape.find('.//v:textbox', document.nsmap)
if textbox is not None:
# 需要特殊处理legacy格式
yield textbox
```
### 新增函数:翻译文本框内容
```python
def _translate_textboxes(document, engine: str) -> int:
"""
翻译文档中所有文本框内容
Args:
document: Document对象
engine: 翻译引擎
Returns:
成功翻译的文本框段落数量
"""
from docx.oxml.ns import qn
from docx.text.paragraph import Paragraph
translated_count = 0
textbox_index = 0
for textbox_content in _find_all_textboxes(document):
textbox_index += 1
logger.debug("处理文本框 #%d", textbox_index)
# 获取文本框内的所有段落
paragraphs = textbox_content.findall('.//w:p', document.nsmap)
for p_element in paragraphs:
# 创建Paragraph对象以便处理
try:
# 直接操作XML元素
# 查找所有t:txbtRun或w:t元素(文本内容)
text_elements = p_element.findall('.//w:t', document.nsmap)
if not text_elements:
continue
# 获取原始文本
original_text = ''.join([t.text for t in text_elements if t.text])
if not original_text.strip():
continue
# 检查是否需要翻译
if not is_translatable(original_text):
logger.debug("文本框跳过(无需翻译): %s", original_text[:30])
continue
# 执行翻译
try:
translated_text = translate_text(
original_text,
engine=engine,
from_lang=SOURCE_LANGUAGE,
to_lang=TARGET_LANGUAGE
)
if translated_text and translated_text != original_text:
# 替换文本内容
# 简单处理:将所有文本替换为翻译后的文本
for i, t in enumerate(text_elements):
if i == 0:
t.text = translated_text
else:
t.text = ""
translated_count += 1
logger.info("文本框翻译[%d]: %s -> %s",
textbox_index, original_text[:30], translated_text[:30])
else:
logger.debug("文本框翻译失败或未翻译: %s", original_text[:30])
except Exception as e:
logger.warning("文本框段落翻译异常: %s", e)
except Exception as e:
logger.warning("文本框段落处理异常: %s", e)
logger.info("文本框翻译完成: 总数=%d, 翻译=%d", textbox_index, translated_count)
return translated_count
```
### 修改主函数
```python
def internationalize_document(input_path: str, output_path: Optional[str] = None,
engine: str = "google") -> str:
"""
文档国际化转换(主函数)
"""
# ... 现有段落处理 ...
# ... 现有表格处理 ...
# ... 现有页眉页脚处理 ...
# 新增:处理文本框
textbox_count = _translate_textboxes(doc, engine)
# ... 保存文档 ...
```
---
## 7. 后续优化
- [ ] 处理嵌套文本框
- [ ] 处理文本框内的表格
- [ ] 处理艺术字内容翻译
- [ ] 支持SmartArt内容翻译
- [ ] 添加翻译预览功能
---
## 8. 实施状态
| 项目 | 状态 | 说明 |
|------|------|------|
| 问题分析 | ✅ 完成 | 已分析根本原因 |
| 解决方案设计 | ✅ 完成 | 已设计解决方案 |
| 代码实现 | ✅ 完成 | 已实现文本框翻译功能 |
| 测试验证 | 待进行 | 需要使用问题文档测试 |
| 文档更新 | ✅ 完成 | 已更新文档 |
# 问题描述
## 问题现象
- 执行代码转换英文后,文档目录页中的内容没有同步转换,能否在文档翻译完成后自动更新目录域,文档可见[AuxiliaryTool/DocumentAutoOptimizationCalibration/reports/新统一平台自动化部署操作指导_英文版.docx]
\ No newline at end of file
# 问题描述
## 问题现象
- 执行代码转换英文后,文档页眉中文字没有成功转换,文档可见[AuxiliaryTool/DocumentAutoOptimizationCalibration/reports/新统一平台自动化部署操作指导_英文版.docx]
\ No newline at end of file
[
{
"system_type": "new_platform",
"system_front_url": "https://192.168.5.44",
"system_back_url": "https://192.168.5.44/#/LoginAdmin",
"username": "admin@xty",
"password": "Ubains@4321",
"code": "csba"
},
{
"system_type": "new_platform",
"system_front_url": "https://192.168.5.44",
"system_back_url": "https://192.168.5.44/#/LoginAdmin",
"username": "admin@xty",
"password": "Ubains@4321",
"code": "csba"
}
]
\ No newline at end of file
# 自动化测试用例生成 - 执行问题修复文档
## 文档信息
- **创建日期**: 2026-03-06
- **版本**: v1.1
- **最后更新**:
- **状态**:
---
## 问题概述
通过main.py运行时,日志打印未找到元素用户、账号、username、account、用户,登录失败。
---
## 发现的问题清单
### 问题1: 无法登录系统
**问题描述**
- 登录失败,未找到用户名输入框
**问题日志**
- 2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 用户名
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 账号
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: username
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: account
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 用户
2026-03-06 13:25:37 - LoginHandler - ERROR - 未找到用户名输入框
2026-03-06 13:25:37 - TestCaseGeneratorMain - ERROR - 执行过程中发生错误: 登录失败
2026-03-06 13:25:37 - TestCaseGeneratorMain - INFO - 正在清理资源...
2026-03-06 13:25:37 - BrowserOperator - INFO - 正在关闭浏览器
2026-03-06 13:25:37 - BrowserOperator - INFO - 浏览器已关闭
2026-03-06 13:25:37 - TestCaseGeneratorMain - INFO - 资源清理完成
2026-03-06 13:41:51 - LoginHandler - WARNING - 未找到匹配元素: [XPATH] //input[@placeholder='请输入账号或手机号或邮箱号']
2026-03-06 13:41:51 - LoginHandler - ERROR - 使用定位器未找到元素: //input[@placeholder='请输入账号或手机号或邮箱号']
2026-03-06 13:41:51 - LoginHandler - ERROR - 填写用户名失败
2026-03-06 13:41:51 - TestCaseGeneratorMain - ERROR - 执行过程中发生错误: 登录失败
2026-03-06 13:41:51 - TestCaseGeneratorMain - INFO - 正在清理资源...
2026-03-06 13:41:51 - BrowserOperator - INFO - 正在关闭浏览器
2026-03-06 13:41:51 - BrowserOperator - INFO - 浏览器已关闭
2026-03-06 13:41:51 - TestCaseGeneratorMain - INFO - 资源清理完成
错误: 登录失败
---
**文档结束**
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论