乐于分享
好东西不私藏

造一个测试用例生成器:从需求文档到结构化用例(附完整代码)

造一个测试用例生成器:从需求文档到结构化用例(附完整代码)

上篇结尾说了,从这篇开始进入实战板块——把前面三篇讲的Agent骨架、Prompt链、API接入全部串起来,造能落地的工具。

第一个就是测试用例生成器。

为什么选这个打头?因为它是我造的第一个真正"能用"的AI测试工具,也是踩坑最多的一个。前面三篇里提到的所有坑——Prompt太笼统、步骤衔接没容错、数据格式不对齐——基本都是在造这个工具的过程中踩的。

但那些是"基础搭建"的坑。真正把它从一个"能跑的脚本"变成"团队愿意用的工具",又踩了一轮完全不同的坑。

第一版:技术上跑通了,实际上没人用

我的第一版用例生成器,就是S4-03里写的那个三步Agent:拆维度→写用例→格式化。技术上完全跑通,输入PRD文本,30秒输出一批结构化用例。

我特别兴奋地拿给同事用。她试了一次,说:"挺好的,但我还是自己写比较快。"

我当时不服气。跟着她看了一遍她怎么写用例的:她拿到PRD后,先花5分钟通读,在关键段落画线标记;然后打开团队的Excel模板,按模块填用例;写的过程中她会翻看历史用例,看看类似功能上次怎么测的;写完还会跟开发确认几个边界场景。

我的工具帮她省了什么?省了"把PRD读一遍然后想测试点"这个环节。但这只是她写用例流程的第一步。后面的查历史用例、跟开发确认、填团队模板——这些我的工具都没覆盖。

你的工具如果只解决了用户工作流中的一个环节,而剩下的环节还得手动做,那用户切换工具的成本可能比直接手动做还高。

这是第一版最大的问题:我只想着"用AI生成用例",没想过"生成用例之后呢?"

第二版:补上"最后一公里"

第二版做了三件事,每件都是被现实打脸之后的反应。

第一件:导入团队Excel模板。

第一版输出的用例格式是我自己定义的JSON。同事拿过去还得手动转成团队模板格式。这个"最后一公里"把工具的效率优势全吃了。

我找同事要了团队的Excel模板,分析了一下结构——用例编号、模块、场景描述、前置条件、操作步骤、预期结果、优先级、备注。然后写了一个导出模块,直接按模板格式输出Excel。

这一步做完之后,同事的态度从"挺好的但我不太用"变成了"偶尔用一下"。进步了,但还不够。

第二件:加历史用例参考。

同事写用例时会翻历史用例,看看类似功能上次怎么测的。我的工具完全没这个能力——它只看当前PRD,不知道这个功能以前怎么测的。

我加了一步:在拆完维度之后,让Agent去测试平台搜同一模块的历史用例,把相关的作为参考喂给"写用例"那一步的Prompt。

这一步效果出乎意料地好。AI生成的用例质量明显提升——因为有了历史用例做参考,它不再只基于PRD字面内容"猜"测试点,而是结合了团队过去的测试经验。生成的用例风格也更贴近团队习惯,同事说"看着像是我们组的人写的"。

但这一步也带来了新问题:历史用例有时候太多,全喂进去会超token限制。我的处理方式是让Agent先筛选——只取和当前功能相关的、近三个月的、最多20条。筛选逻辑本身又是一个Prompt,加了十行代码,但效果天差地别。不筛选的时候,200条历史用例一股脑塞进去,大模型注意力被稀释,生成的用例反而比不加参考还差。

加参考数据不是越多越好,是"筛过的好"。这个教训后来我用在所有需要喂上下文的工具上。

第三件:加"覆盖率自查"。

这是被同事一句话逼出来的。有一次我拿生成的用例给她看,她扫了一眼说:"你这个登录模块的用例,没覆盖到'记住登录态'。"

我回去查,PRD里确实提了"记住登录态",但AI在拆维度的时候把它归到"登录功能"下面,写用例的时候又只写了"登录成功"和"登录失败","记住登录态"被遗漏了。

这不是S4-03里讲的"大模型遗忘排在前面的测试点"——那次是注意力衰减。这次是理解偏差:它把"记住登录态"理解成了登录功能的一个子项,而不是一个独立的测试场景。

我在格式化输出之前加了一个"覆盖率自查"步骤:让Agent拿生成的用例列表,回头对照PRD原文,逐段检查"这段内容是否有对应的用例覆盖",没有就标注出来。

# 覆盖率自查:用例列表 vs PRD原文,找遗漏coverage_gaps = call_llm(prompt_coverage_check, {    "prd": prd_text,    "test_cases": generated_cases})# 输出示例:["PRD第3段'记住登录态'无对应用例", "PRD第5段'第三方登录'无对应用例"]

这个自查步骤不是100%准确——它有时候会漏报,有时候会误报。但即使只有70%的准确率,它帮我发现的遗漏也比我自己逐条核对快得多。AI自查不需要完美,只需要比你手动检查快就够了。

第三版:让AI说出"我不知道的地方"

第二版跑了一段时间后,我发现一个规律:AI生成的用例大概能覆盖70%-80%的场景,剩下的20%-30%是它想不到的——要么是业务规则太隐蔽(比如"连续3次输错密码后锁定账号,但VIP用户锁定后可以自助解锁"),要么是跨模块联动(比如"登录失败后同时触发风控告警")。

这些场景AI想不到,因为它没有业务上下文。但它知道"这里可能有问题"——前提是你给它一个机会说出来。

我在工具里加了一个"人工补充"环节:AI生成完用例后,不是直接导出,而是先展示一个"不确定区域"列表——它标注自己在生成过程中"不确定"或"觉得可能遗漏"的地方。然后我花两分钟补充,再导出。

这个环节的关键不是补充了多少,而是让AI说出"我不确定"。一个诚实的"我不确定"比一个自信的"我覆盖了所有场景"有价值得多。

实际用下来,"不确定区域"平均每次列3-5条,其中大概2条是我确实需要补的。这2条往往是业务逻辑相关的——AI看到了PRD里的某个描述,但它不确定这个描述意味着什么测试场景,就标注出来问我。这比我自己逐段读PRD找遗漏高效得多。

完整链路长什么样

三版迭代之后,用例生成器的完整链路是这样的:

  1. 输入PRD文本 → 拆测试维度(人工确认)

  2. 搜历史用例 → 筛选相关的近20条作为参考

  3. 按维度+历史参考 → 写具体用例(超过8个测试点自动分批)

  4. 覆盖率自查 → 标注PRD中未被覆盖的段落

  5. 人工补充 → 我花2分钟补"不确定区域"

  6. 按团队模板 → 导出Excel

  7. 调API → 写入测试平台(确认后写入)

从第一版的"30秒生成一堆JSON",到第三版的"3分钟走完全链路,直接入库"。慢了两分半,但从"没人用"变成了"每周用三次以上"。

同事后来跟我说了句话,我觉得是对这个工具最好的评价:"以前你那个工具是帮你干活的,现在这个是帮我干活的。"区别在哪?第一版是从"我能做什么"出发设计的,第三版是从"她需要什么"出发设计的。

工具的价值不在于"多快生成",而在于"生成的东西能不能直接用"。

完整代码如下:

"""测试用例生成器 - 完整实现从需求文档(PRD)到结构化用例,完整链路依赖安装:    pip install openai openpyxl requests使用方式:    python test_case_generator.py --prd prd.txt --project "登录模块""""import jsonimport timeimport argparsefrom pathlib import Pathfrom openai import OpenAIfrom openpyxl import Workbookfrom openpyxl.styles import Font, Alignment, PatternFillimport requests# ============================================================# 配置区# ============================================================# 大模型配置LLM_API_KEY = "your-api-key-here"LLM_BASE_URL = "https://api.openai.com/v1"LLM_MODEL = "gpt-4o-mini"# 测试平台API配置PLATFORM_BASE_URL = "https://test-platform.company.com/api/v1"PLATFORM_TOKEN = "your-platform-token-here"# 批处理参数BATCH_SIZE = 6  # 超过6个测试点自动分批,6是舒适区(S4-03经验)HISTORY_CASE_LIMIT = 20  # 历史用例最多取20条# 初始化大模型客户端llm_client = OpenAI(api_key=LLM_API_KEY, base_url=LLM_BASE_URL)# ============================================================# 大模型调用封装# ============================================================def call_llm(prompt: str, input_data: str, temperature: float = 0.3) -> str:    """    调用大模型,返回纯文本响应。    temperature默认0.3,测试场景需要稳定性而非创造性。    Args:        prompt: 系统提示词/指令        input_data: 输入内容(PRD文本、上一步输出等)        temperature: 温度参数    Returns:        大模型返回的文本    """    try:        response = llm_client.chat.completions.create(            model=LLM_MODEL,            messages=[                {"role""system""content": prompt},                {"role""user""content": input_data}            ],            temperature=temperature,            response_format={"type""json_object"}  # 强制JSON输出        )        return response.choices[0].message.content    except Exception as e:        print(f"[ERROR] LLM调用失败: {e}")        raisedef call_llm_json(prompt: str, input_data: str, temperature: float = 0.3) -> dict:    """    调用大模型并解析为JSON dict。    自动处理大模型偶尔返回非标准JSON的情况。    """    raw = call_llm(prompt, input_data, temperature)    try:        return json.loads(raw)    except json.JSONDecodeError:        # 尝试提取JSON块(大模型有时会包裹markdown代码块)        import re        match = re.search(r'\{.*\}|\[.*\]', raw, re.DOTALL)        if match:            return json.loads(match.group())        raise ValueError(f"无法解析LLM输出为JSON: {raw[:200]}")# ============================================================# 人工确认环节# ============================================================def confirm(label: str, content: str) -> bool:    """    人工确认环节。打印内容,等待用户确认。    这是"暂停键"——在关键步骤留人工把关。    Args:        label: 步骤名称        content: 待确认内容    Returns:        True=确认通过,False=需要重跑    """    print(f"\n{'='*60}")    print(f"【人工确认】{label}")    print(f"{'='*60}")    print(content)    print(f"{'='*60}")    while True:        choice = input("确认无误?(y=继续 / n=重跑此步 / q=退出): ").strip().lower()        if choice == 'y':            return True        elif choice == 'n':            return False        elif choice == 'q':            print("用户退出。")            exit(0)        else:            print("请输入 y/n/q")# ============================================================# Prompt 模板(每一步的指令)# ============================================================# 第一步:拆测试维度PROMPT_SPLIT_DIMENSIONS = """\你是一位资深测试工程师。请分析以下需求文档,按以下五个维度拆分测试点:1. 功能验证2. 边界值3. 异常场景4. 性能关注点5. 安全风险每个维度列出3-5个测试点,测试点要具体、可测试。输出格式(JSON):{  "dimensions": [    {"维度": "功能验证", "测试点": ["登录成功", "登录失败", "密码错误"]},    {"维度": "边界值", "测试点": ["密码最短长度", "密码最长长度"]}  ]}注意:- 测试点要基于需求文档内容,不要凭空捏造- 每个维度的测试点不超过5个- 如果某个维度在需求中没有相关内容,返回空数组"""# 第二步:按维度+历史参考写用例PROMPT_GENERATE_CASES = """\你是一位资深测试工程师。请根据以下测试维度和历史用例参考,编写具体的测试用例。输入数据格式(JSON):{  "dimensions": [...],  // 测试维度和测试点  "history_cases": [...]  // 同模块历史用例参考(可能为空)}每条用例必须包含以下字段:- 模块: 功能模块名称- 场景描述: 一句话说清楚测什么场景- 前置条件: 执行用例前需要满足的条件- 操作步骤: 逐步描述操作(数组,每步一个字符串)- 预期结果: 执行后应该出现的结果- 优先级: P0/P1/P2- 备注: 特殊说明(可为空字符串)输出格式(JSON):{  "cases": [    {      "模块": "登录",      "场景描述": "正确的账号密码登录成功",      "前置条件": "账号已注册且状态正常",      "操作步骤": ["打开登录页面", "输入正确账号", "输入正确密码", "点击登录按钮"],      "预期结果": "登录成功,跳转到首页",      "优先级": "P0",      "备注": ""    }  ]}要求:- 参考历史用例的写法风格,但不要照搬- 每个测试点至少生成一条用例- 操作步骤要具体到可执行,不要写"执行相关操作"这种模糊描述"""# 覆盖率自查PROMPT_COVERAGE_CHECK = """\你是一位测试质量审查员。请对照需求文档原文,检查测试用例的覆盖率。输入数据(JSON):{  "prd": "需求文档原文",  "test_cases": [生成的测试用例列表]}请逐段检查需求文档,找出"有需求描述但没有对应测试用例"的地方。输出格式(JSON):{  "gaps": [    {      "prd段落": "需求文档中的原文片段",      "遗漏说明": "为什么没有覆盖到",      "建议": "应该补充什么用例"    }  ],  "coverage_rate": "预估覆盖率(如85%)"}注意:- 只报告确实遗漏的,不要把"已覆盖但不够细"的算作遗漏- 如果所有段落都有覆盖,返回空gaps数组"""# 不确定区域标注PROMPT_UNCERTAINTY = """\你是一位测试工程师。在刚才生成测试用例的过程中,有些地方你可能不确定。请回顾你生成的用例和需求文档,标注你"不确定"或"觉得可能遗漏"的地方。不确定的典型情况:- 需求文档描述模糊,你做了假设但不确定是否正确- 涉及业务规则,你没有足够的上下文判断- 跨模块联动,你不确定是否需要测试- 边界值不明确,你按常规处理但可能有特殊情况输出格式(JSON):{  "uncertainties": [    {      "区域": "不确定的具体内容",      "原因": "为什么不确定",      "建议操作": "建议人工确认什么"    }  ]}注意:- 诚实标注,不确定就说不确定- 不要为了显得全面而编造不确定项- 如果确实没有不确定的地方,返回空数组"""# ============================================================# 测试平台API封装(S4-05的封装层)# ============================================================class TestPlatformAPI:    """    测试平台API封装。    所有HTTP请求细节集中在这里,主流程只调用方法。    换平台/换接口/换鉴权方式,只改这里。    """    def __init__(self, base_url: str, token: str):        self.base_url = base_url.rstrip("/")        self.session = requests.Session()        self.session.headers.update({            "Authorization"f"Bearer {token}",            "Content-Type""application/json"        })    def search_cases(self, module_name: str, limit: int = 20) -> list:        """        搜索同模块的历史用例。        用于第二步的历史参考。        """        url = f"{self.base_url}/testcases/search"        params = {            "module": module_name,            "limit": limit,            "sort""update_time:desc"  # 按更新时间倒序,取最新的        }        resp = self.session.get(url, params=params, timeout=10)        resp.raise_for_status()        data = resp.json()        # 实际API返回可能包了一层壳,取data字段        if isinstance(data, dictand "data" in data:            return data["data"]        return data    def create_case(self, case_data: dict) -> dict:        """        创建单条测试用例。        返回平台返回的用例信息(含用例ID)。        """        url = f"{self.base_url}/testcases"        resp = self.session.post(url, json=case_data, timeout=10)        resp.raise_for_status()        return resp.json()    def batch_create_cases(self, cases: list) -> list:        """        批量创建测试用例。        返回每条用例的创建结果(含用例ID和平台链接)。        """        results = []        for i, case in enumerate(cases):            try:                result = self.create_case(case)                results.append({                    "场景描述"case.get("场景描述"""),                    "status""success",                    "case_id": result.get("data", {}).get("id"""),                    "url": result.get("data", {}).get("url""")                })                print(f"  [{i+1}/{len(cases)}] 创建成功: {case.get('场景描述''')}")            except Exception as e:                results.append({                    "场景描述"case.get("场景描述"""),                    "status""failed",                    "error"str(e)                })                print(f"  [{i+1}/{len(cases)}] 创建失败: {case.get('场景描述''')} - {e}")            # 避免请求太快被限流            time.sleep(0.3)        return results# ============================================================# 数据转换层(S4-05:大模型输出 → API格式)# ============================================================def transform_cases_for_platform(cases: list, project_name: str) -> list:    """    将大模型输出的用例格式,转换为测试平台API要求的格式。    字段映射、结构重组、默认值填充都在这里。    大模型输出格式:        {"模块": "...", "场景描述": "...", "操作步骤": [...], "预期结果": "...", ...}    平台API格式:        {"name": "...", "module": "...", "test_steps": [{"action": "...", "expected": "..."}],         "expect_result": "...", "priority": "...", "project": "..."}    """    transformed = []    for case in cases:        # 字段映射        platform_case = {            "name"case.get("场景描述"""),            "module"case.get("模块"""),            "project": project_name,            "priority"case.get("优先级""P2"),            "precondition"case.get("前置条件"""),            "expect_result"case.get("预期结果"""),        }        # 操作步骤转换:从字符串数组转为对象数组        steps = case.get("操作步骤", [])        if isinstance(steps, list):            platform_case["test_steps"] = [                {"action": step, "expected"""for step in steps            ]        elif isinstance(steps, str):            platform_case["test_steps"] = [{"action": steps, "expected"""}]        else:            platform_case["test_steps"] = []        # 必填字段校验        if not platform_case["name"]:            print(f"  [WARN] 用例缺少场景描述,跳过: {case}")            continue        if not platform_case["test_steps"]:            print(f"  [WARN] 用例缺少操作步骤,跳过: {platform_case['name']}")            continue        transformed.append(platform_case)    return transformeddef transform_history_for_llm(history_cases: list) -> list:    """    将平台返回的历史用例,转换为大模型参考用的精简格式。    平台返回的字段很多,只取有用的,避免token浪费。    """    simplified = []    for case in history_cases[:HISTORY_CASE_LIMIT]:        simplified.append({            "场景"case.get("name"""),            "步骤": [s.get("action"""if isinstance(s, dictelse str(s)                    for s in case.get("test_steps", [])],            "预期"case.get("expect_result"""),            "优先级"case.get("priority""")        })    return simplified# ============================================================# Excel导出模块(第二版新增)# ============================================================def export_to_excel(cases: list, output_path: str, coverage_gaps: list = None):    """    按团队模板格式导出Excel。    包含用例明细和覆盖率自查报告两个Sheet。    """    wb = Workbook()    # --- Sheet1: 测试用例 ---    ws = wb.active    ws.title = "测试用例"    # 表头样式    header_font = Font(bold=True, size=11)    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")    header_font_white = Font(bold=True, size=11, color="FFFFFF")    headers = ["用例编号""模块""场景描述""前置条件""操作步骤",               "预期结果""优先级""备注"]    for col, header in enumerate(headers, 1):        cell = ws.cell(row=1, column=col, value=header)        cell.font = header_font_white        cell.fill = header_fill        cell.alignment = Alignment(horizontal="center", vertical="center")    # 填充用例数据    for idx, case in enumerate(cases, 1):        steps = case.get("操作步骤", [])        if isinstance(steps, list):            steps_text = "\n".join(f"{i}{s}" for i, s in enumerate(steps, 1))        else:            steps_text = str(steps)        row_data = [            f"TC-{idx:04d}",            case.get("模块"""),            case.get("场景描述"""),            case.get("前置条件"""),            steps_text,            case.get("预期结果"""),            case.get("优先级""P2"),            case.get("备注""")        ]        for col, value in enumerate(row_data, 1):            cell = ws.cell(row=idx + 1, column=col, value=value)            cell.alignment = Alignment(wrap_text=True, vertical="top")    # 列宽设置    col_widths = [121530254030815]    for col, width in enumerate(col_widths, 1):        ws.column_dimensions[chr(64 + col)].width = width    # --- Sheet2: 覆盖率自查报告 ---    if coverage_gaps:        ws2 = wb.create_sheet("覆盖率自查")        gap_headers = ["PRD段落""遗漏说明""建议补充"]        for col, header in enumerate(gap_headers, 1):            cell = ws2.cell(row=1, column=col, value=header)            cell.font = header_font_white            cell.fill = PatternFill(start_color="ED7D31", end_color="ED7D31", fill_type="solid")            cell.alignment = Alignment(horizontal="center")        for idx, gap in enumerate(coverage_gaps, 2):            ws2.cell(row=idx, column=1, value=gap.get("prd段落"""))            ws2.cell(row=idx, column=2, value=gap.get("遗漏说明"""))            ws2.cell(row=idx, column=3, value=gap.get("建议"""))            for col in range(14):                ws2.cell(row=idx, column=col).alignment = Alignment(wrap_text=True, vertical="top")        ws2.column_dimensions["A"].width = 40        ws2.column_dimensions["B"].width = 35        ws2.column_dimensions["C"].width = 35    wb.save(output_path)    print(f"\n[INFO] Excel已导出: {output_path}")# ============================================================# 分批处理逻辑(S4-03:超过BATCH_SIZE自动分批)# ============================================================def generate_cases_batched(dimensions: list, history_ref: list) -> list:    """    按维度分批生成用例。    超过BATCH_SIZE个测试点就分批,结果合并。    防止大模型注意力衰减导致后面的测试点被"遗忘"。    """    # 展开所有测试点    all_points = []    for dim in dimensions:        for point in dim.get("测试点", []):            all_points.append({"维度": dim["维度"], "测试点": point})    print(f"\n[INFO] 共 {len(all_points)} 个测试点,BATCH_SIZE={BATCH_SIZE}")    # 分批    if len(all_points) <= BATCH_SIZE:        batches = [all_points]    else:        batches = [all_points[i:i + BATCH_SIZE]                   for i in range(0len(all_points), BATCH_SIZE)]        print(f"[INFO] 测试点超过{BATCH_SIZE}个,分 {len(batches)} 批处理")    # 逐批生成    all_cases = []    for i, batch in enumerate(batches):        print(f"\n[INFO] 正在处理第 {i+1}/{len(batches)} 批...")        input_data = json.dumps({            "dimensions": batch,            "history_cases": history_ref        }, ensure_ascii=False)        result = call_llm_json(PROMPT_GENERATE_CASES, input_data)        batch_cases = result.get("cases", [])        all_cases.extend(batch_cases)        print(f"[INFO] 第 {i+1} 批生成 {len(batch_cases)} 条用例")    return all_cases# ============================================================# 主流程:完整7步链路# ============================================================def generate_test_cases(prd_text: str, project_name: str,                        output_excel: str = None,                        write_to_platform: bool = False) -> list:    """    测试用例生成器主流程。    完整链路:    1. 输入PRD → 拆测试维度(人工确认)    2. 搜历史用例 → 筛选相关的作为参考    3. 按维度+历史参考 → 写具体用例(自动分批)    4. 覆盖率自查 → 标注PRD中未被覆盖的段落    5. 人工补充 → 补"不确定区域"    6. 按团队模板 → 导出Excel    7. 调API → 写入测试平台(确认后写入)    Args:        prd_text: PRD需求文档文本        project_name: 项目/模块名称        output_excel: Excel输出路径(为空则不导出)        write_to_platform: 是否写入测试平台    Returns:        生成的测试用例列表    """    print(f"\n{'#'*60}")    print(f"# 测试用例生成器启动")    print(f"# 项目: {project_name}")    print(f"# PRD长度: {len(prd_text)} 字符")    print(f"{'#'*60}")    # ============================================================    # Step 1: 拆测试维度    # ============================================================    print("\n--- Step 1/7: 拆测试维度 ---")    while True:        result = call_llm_json(PROMPT_SPLIT_DIMENSIONS, prd_text)        dimensions = result.get("dimensions", [])        # 展示拆分结果        display = []        for dim in dimensions:            points = dim.get("测试点", [])            display.append(f"【{dim['维度']}{' | '.join(points) if points else'(空)'}")        ok = confirm("测试维度拆分""\n".join(display))        if ok:            break        print("[INFO] 重新拆分维度...")    total_points = sum(len(d.get("测试点", [])) for d in dimensions)    print(f"\n[INFO] 维度拆分完成: {len(dimensions)}个维度, {total_points}个测试点")    # ============================================================    # Step 2: 搜索历史用例作为参考    # ============================================================    print("\n--- Step 2/7: 搜索历史用例 ---")    history_ref = []    try:        platform = TestPlatformAPI(PLATFORM_BASE_URL, PLATFORM_TOKEN)        raw_history = platform.search_cases(module_name=project_name, limit=HISTORY_CASE_LIMIT)        # 转换为精简格式,避免token浪费        history_ref = transform_history_for_llm(raw_history)        print(f"[INFO] 找到 {len(raw_history)} 条历史用例,精简后取 {len(history_ref)} 条作为参考")    except Exception as e:        print(f"[WARN] 获取历史用例失败(不影响主流程): {e}")        print("[INFO] 将不使用历史参考,仅基于PRD生成")    # ============================================================    # Step 3: 按维度 + 历史参考 → 写具体用例(自动分批)    # ============================================================    print("\n--- Step 3/7: 生成测试用例 ---")    all_cases = generate_cases_batched(dimensions, history_ref)    print(f"\n[INFO] 共生成 {len(all_cases)} 条测试用例")    # ============================================================    # Step 4: 覆盖率自查    # ============================================================    print("\n--- Step 4/7: 覆盖率自查 ---")    coverage_input = json.dumps({        "prd": prd_text,        "test_cases": all_cases    }, ensure_ascii=False)    coverage_result = call_llm_json(PROMPT_COVERAGE_CHECK, coverage_input)    gaps = coverage_result.get("gaps", [])    coverage_rate = coverage_result.get("coverage_rate""未知")    print(f"[INFO] 预估覆盖率: {coverage_rate}")    if gaps:        print(f"[INFO] 发现 {len(gaps)} 处遗漏:")        for i, gap in enumerate(gaps, 1):            print(f"  {i}. PRD段落: {gap.get('prd段落''')[:50]}...")            print(f"     遗漏说明: {gap.get('遗漏说明''')}")            print(f"     建议: {gap.get('建议''')}")    else:        print("[INFO] 未发现明显遗漏")    # ============================================================    # Step 5: 人工补充(不确定区域 + 遗漏补充)    # ============================================================    print("\n--- Step 5/7: 人工补充 ---")    # 标注不确定区域    uncertainty_input = json.dumps({        "prd": prd_text,        "test_cases": all_cases    }, ensure_ascii=False)    uncertainty_result = call_llm_json(PROMPT_UNCERTAINTY, uncertainty_input)    uncertainties = uncertainty_result.get("uncertainties", [])    if uncertainties:        print(f"\n[AI标注的"不确定区域"](共{len(uncertainties)}处):")        for i, u in enumerate(uncertainties, 1):            print(f"  {i}. 区域: {u.get('区域''')}")            print(f"     原因: {u.get('原因''')}")            print(f"     建议: {u.get('建议操作''')}")    print(f"\n[覆盖率遗漏](共{len(gaps)}处):")    for i, gap in enumerate(gaps, 1):        print(f"  {i}{gap.get('prd段落''')[:60]}")        print(f"     建议: {gap.get('建议''')}")    # 展示生成的用例供确认    case_display = []    for idx, case in enumerate(all_cases, 1):        case_display.append(            f"{idx}. [{case.get('优先级''P2')}{case.get('场景描述''')} "            f"({case.get('模块''')})"        )    ok = confirm(        "用例确认(请检查后决定是否继续)",        "\n".join(case_display) + f"\n\n覆盖率: {coverage_rate}"        f"\n遗漏: {len(gaps)}处 | 不确定: {len(uncertainties)}处"    )    if not ok:        print("[INFO] 用例需要调整,重新生成...")        # 这里可以选择重新生成或让用户手动编辑后继续        # 简化处理:直接退出,用户修改PRD后重跑        print("[提示] 如需调整,请修改PRD后重新运行,或手动编辑生成的用例")        return all_cases    # ============================================================    # Step 6: 导出Excel    # ============================================================    if output_excel:        print(f"\n--- Step 6/7: 导出Excel ---")        export_to_excel(all_cases, output_excel, gaps)    # ============================================================    # Step 7: 写入测试平台(确认后写入)    # ============================================================    if write_to_platform:        print("\n--- Step 7/7: 写入测试平台 ---")        ok = confirm("写入测试平台",                      f"即将写入 {len(all_cases)} 条用例到测试平台\n"                      f"项目: {project_name}\n"                      f"平台: {PLATFORM_BASE_URL}\n\n"                      f"确认写入?")        if not ok:            print("[INFO] 跳过写入平台,用例已导出到本地")            return all_cases        # 数据转换:大模型格式 → 平台API格式        platform_cases = transform_cases_for_platform(all_cases, project_name)        print(f"[INFO] 数据转换完成: {len(platform_cases)} 条用例待写入")        # 批量写入        platform = TestPlatformAPI(PLATFORM_BASE_URL, PLATFORM_TOKEN)        results = platform.batch_create_cases(platform_cases)        # 统计结果        success_count = sum(1 for r in results if r["status"] == "success")        failed_count = len(results) - success_count        print(f"\n[结果] 写入完成: 成功 {success_count} 条, 失败 {failed_count} 条")        # 打印平台链接        for r in results:            if r["status"] == "success" and r.get("url"):                print(f"  - {r['场景描述']}{r['url']}")    print(f"\n{'#'*60}")    print(f"# 测试用例生成完成!")    print(f"# 共 {len(all_cases)} 条用例")    print(f"# 覆盖率: {coverage_rate}")    if output_excel:        print(f"# Excel: {output_excel}")    print(f"{'#'*60}")    return all_cases# ============================================================# 命令行入口# ============================================================def main():    parser = argparse.ArgumentParser(description="测试用例生成器")    parser.add_argument("--prd", required=Truehelp="PRD文件路径")    parser.add_argument("--project", required=Truehelp="项目/模块名称")    parser.add_argument("--excel", default=Nonehelp="Excel输出路径(不指定则不导出)")    parser.add_argument("--platform", action="store_true"help="是否写入测试平台")    args = parser.parse_args()    # 读取PRD    prd_path = Path(args.prd)    if not prd_path.exists():        print(f"[ERROR] PRD文件不存在: {prd_path}")        return    prd_text = prd_path.read_text(encoding="utf-8")    if not prd_text.strip():        print("[ERROR] PRD文件为空")        return    # 默认Excel输出路径    excel_path = args.excel or f"测试用例_{args.project}_{int(time.time())}.xlsx"    # 运行    generate_test_cases(        prd_text=prd_text,        project_name=args.project,        output_excel=excel_path,        write_to_platform=args.platform    )if __name__ == "__main__":    main()

一句话总结:造用例生成器最大的坑不是技术跑不通,是"技术上跑通了但没人用"——补上团队模板、历史参考、覆盖率自查、人工补充四个环节,工具从"能生成"变成"能用"。AI自查不需要完美,比你手动检查快就够了。

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-08 11:44:05 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/893620.html
  2. 运行时间 : 0.200810s [ 吞吐率:4.98req/s ] 内存消耗:4,703.55kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=8fd2803b0ab48dab3b9f65f1eb39b15e
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 4.22 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.11 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001023s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001683s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000688s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000657s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001326s ]
  6. SELECT * FROM `set` [ RunTime:0.000489s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001380s ]
  8. SELECT * FROM `article` WHERE `id` = 893620 LIMIT 1 [ RunTime:0.001521s ]
  9. UPDATE `article` SET `lasttime` = 1786160645 WHERE `id` = 893620 [ RunTime:0.007156s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000725s ]
  11. SELECT * FROM `article` WHERE `id` < 893620 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001300s ]
  12. SELECT * FROM `article` WHERE `id` > 893620 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001932s ]
  13. SELECT * FROM `article` WHERE `id` < 893620 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002226s ]
  14. SELECT * FROM `article` WHERE `id` < 893620 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.002485s ]
  15. SELECT * FROM `article` WHERE `id` < 893620 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.005051s ]
0.202522s