基于LangChain与Qwen实现手工测试用例自动转接口自动化脚本

📅 2026/7/2 23:16:12 ✍️ 编辑团队 👁️ 阅读次数
基于LangChain与Qwen实现手工测试用例自动转接口自动化脚本
1. 项目概述从手工到自动化的测试革命在软件测试领域手工测试用例转接口自动化测试这听起来像是一个老生常谈却又始终充满痛点的需求。每个测试工程师的电脑里可能都躺着一份又一份用Excel、Word或者TestLink编写的测试用例文档它们详细描述了每个接口的请求方式、参数、预期结果。然而当我们需要将这些用例转化为可执行的自动化脚本时往往意味着新一轮的“体力劳动”阅读文档、理解业务逻辑、编写代码、处理断言、管理测试数据。这个过程不仅重复、耗时而且极易因为人为疏忽引入错误尤其是在接口数量庞大、业务逻辑复杂的微服务架构下维护成本呈指数级上升。我最近基于LangChain实现了一个工具目标就是终结这种低效的循环。这个工具的核心思路是利用大语言模型的理解与生成能力将自然语言或结构化描述的手工测试用例自动转换为高质量的接口自动化测试代码。这不仅仅是简单的文本转换而是让AI理解测试意图、业务上下文并生成符合团队编码规范、可直接集成到现有测试框架中的脚本。对于测试团队而言这意味着可以将宝贵的人力从重复的编码工作中解放出来更专注于测试策略设计、复杂场景探索和测试深度挖掘。这个工具特别适合那些已经积累了海量手工测试用例但自动化测试覆盖率提升缓慢的团队也适合在敏捷开发中需要快速为新增或变更的接口补充自动化测试的场合。无论是测试开发工程师、质量保障负责人还是希望提升研发效能的全栈开发者都能从这个工具的思路和实现中获益。2. 核心设计思路与架构选型2.1 为什么是LangChain面对“将自然语言测试用例转为代码”这个任务第一个跳入脑海的可能是直接调用大语言模型的API比如OpenAI的GPT系列或国内的通义千问。这确实能完成基础任务但很快就会遇到工程化难题如何管理复杂的提示词如何处理长文档的分割与上下文关联如何集成外部工具比如查询接口文档、获取测试数据模板如何保证生成代码的格式、风格一致性这正是LangChain的用武之地。LangChain不是一个模型而是一个用于构建由LLM驱动的应用程序的框架。它将与大模型交互的复杂过程模块化、链条化。对于我们的工具LangChain提供了几个关键价值模块化提示工程我们可以将“用例理解”、“代码生成”、“代码格式化”等步骤拆解成独立的PromptTemplate使整个流程更清晰、易于调试和优化。上下文管理手工测试用例文档可能很长。LangChain的文本分割器RecursiveCharacterTextSplitter和向量存储检索如Chroma能力可以帮助我们有效地处理长文档让模型只关注与当前转换任务最相关的上下文片段。链式调用这是LangChain的核心概念。我们可以构建一个LLMChain甚至更复杂的SequentialChain来定义从“输入原始用例”到“输出最终代码”的完整流水线。例如先让一个链总结测试要点再让另一个链根据要点生成Python的pytest代码。输出解析模型生成的文本需要被结构化。LangChain的OutputParser如PydanticOutputParser可以强制模型按照我们定义的JSON格式输出方便我们提取函数名、参数、断言语句等元素再进行后续组装。简而言之LangChain为我们提供了将“一个好点子”变成“一个稳定可用工具”的脚手架。2.2 工具整体架构设计整个工具的架构可以看作一个智能化的代码生成流水线其核心流程如下原始测试用例输入 - 文档解析与清洗 - 测试意图分析与信息提取 - 测试代码生成 - 代码后处理与格式化 - 可执行测试脚本输出在这个流程中LangChain充当了“大脑”和“调度中心”的角色。具体组件设计如下输入适配层负责读取不同格式的测试用例如Markdown、Excel、CSV、甚至是截图OCR后的文本。这一层将非结构化的输入初步标准化。信息处理与增强层LangChain核心文档加载与分割使用TextLoader、CSVLoader等加载文档并用RecursiveCharacterTextSplitter按语义进行分割形成一个个知识片段。向量知识库可选组件。将团队的接口文档、历史测试脚本存入如Chroma的向量数据库。在生成代码时可以检索最相关的接口规范作为上下文提高生成准确性。测试用例解析链这是第一个LLM链。它接收分割后的测试用例文本目标是提取出结构化的测试信息。我们使用Pydantic模型来定义输出结构例如class TestCaseInfo(BaseModel): api_name: str Field(description接口名称或标识) method: str Field(descriptionHTTP方法如GET, POST) endpoint: str Field(description接口路径) request_params: Dict[str, Any] Field(description请求参数包括query, body, header) expected_status: int Field(description期望的HTTP状态码) expected_response_schema: Optional[Dict] Field(description期望的响应体结构或关键断言点) test_description: str Field(description测试用例的描述)测试代码生成链这是第二个LLM链。它接收上一步解析出的TestCaseInfo对象并结合“代码模板”和“团队编码规范”的提示词生成具体的测试脚本。这里可以指定生成pytestrequests的代码或者unittest、httpx等风格的代码。输出处理层代码后处理对LLM生成的代码进行清洗例如移除多余的注释、确保缩进正确。格式化调用black、isort等代码格式化工具使生成的代码符合团队规范。集成文件生成将生成的单个测试函数组织成完整的测试模块文件并可能生成对应的conftest.py共享夹具或资源文件。设计心得在架构设计初期不要追求一步到位实现全自动的“黑盒”。一个好的策略是设计成“人机协同”模式。即工具生成代码后允许测试工程师进行快速的审查和微调。这样既能保证效率又能利用人的经验把控最终质量降低对模型生成结果100%准确的依赖。2.3 关键技术栈选型解析LLM核心我选择了Qwen通义千问作为基座模型。原因有几个首先它在代码生成和理解任务上表现优异尤其对中文测试用例的描述理解更到位其次API调用成本相对可控最后支持足够长的上下文窗口便于处理完整的测试用例文档。作为备选DeepSeek-Coder或GPT-4 Turbo也是极佳的选择。应用框架LangChain是毋庸置疑的核心框架。对于需要更复杂工作流和状态管理的场景可以探索LangGraph它允许你以图的形式定义多个LLM节点和工具节点的调用流程比如可以设计一个循环生成代码 - 尝试执行 - 根据执行错误反馈给模型进行修正。但对于大多数转换场景SequentialChain已经足够。向量存储对于需要检索增强生成RAG的场景Chroma是一个轻量级、易嵌入的选择。如果你需要更强大的生产级特性Weaviate或Qdrant是更好的选择。服务与部署使用FastAPI将整个工具包装成RESTful API服务方便与其他系统如测试管理平台、CI/CD流水线集成。FastAPI的自动文档生成和高效异步支持非常适合此类AI应用。代码处理Black和Isort用于代码格式化AST抽象语法树模块可用于更复杂的代码分析和重构。3. 从零构建核心实现步骤拆解3.1 环境准备与依赖安装首先创建一个干净的Python虚拟环境是良好实践的开始。# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 安装核心依赖 pip install langchain langchain-community langchain-core pip install qianfan # 如果使用千帆平台的Qwen或者使用 openai 包配置兼容API pip install chromadb # 向量数据库 pip install fastapi uvicorn # Web服务框架 pip install pydantic # 数据验证 pip install black isort # 代码格式化 pip install python-dotenv # 管理环境变量接下来在项目根目录创建.env文件用于安全地存储你的API密钥等敏感信息。# .env QWEN_API_KEYyour_qwen_api_key_here QWEN_BASE_URLhttps://dashscope.aliyuncs.com/compatible-mode/v1 # 示例以官方为准在代码中通过dotenv加载配置。3.2 构建测试用例解析链这是将自然语言“翻译”成机器可理解结构的关键一步。我们使用LangChain的LLMChain结合PydanticOutputParser。import os from dotenv import load_dotenv from langchain.prompts import PromptTemplate from langchain.output_parsers import PydanticOutputParser from langchain_community.chat_models import ChatOpenAI # 使用兼容OpenAI API的客户端 from langchain.chains import LLMChain from pydantic import BaseModel, Field from typing import Dict, Any, Optional load_dotenv() # 1. 定义我们期望的结构化输出模型 class TestCaseInfo(BaseModel): api_name: str Field(description接口名称或标识) method: str Field(descriptionHTTP方法必须为GET, POST, PUT, DELETE, PATCH之一) endpoint: str Field(description接口路径以/开头) request_params: Dict[str, Any] Field(description请求参数是一个字典。包含headers、query_params、json_body等键。例如{headers: {Content-Type: application/json}, json_body: {username: test, password: 123456}}) expected_status: int Field(description期望的HTTP状态码如200, 201, 400, 404) expected_response_schema: Optional[Dict] Field(description期望的响应体结构或关键断言点。例如{code: 0, message: success, data: {id: number}}) test_description: str Field(description测试用例的业务描述) # 2. 初始化LLM这里以配置为Qwen的ChatOpenAI为例 llm ChatOpenAI( modelqwen-plus, # 或其他Qwen模型如 qwen-turbo openai_api_keyos.getenv(QWEN_API_KEY), openai_api_baseos.getenv(QWEN_BASE_URL), temperature0.1, # 温度调低使输出更确定、稳定 ) # 3. 创建输出解析器 parser PydanticOutputParser(pydantic_objectTestCaseInfo) # 4. 构建提示词模板 prompt_template 你是一个资深的测试开发工程师。请将以下手工测试用例描述解析成结构化的测试信息。 {format_instructions} 测试用例描述{test_case_text}请只输出解析后的JSON对象不要有任何其他解释。 prompt PromptTemplate( templateprompt_template, input_variables[test_case_text], partial_variables{format_instructions: parser.get_format_instructions()}, # 将输出格式说明注入提示词 ) # 5. 创建LLM链 parsing_chain LLMChain(llmllm, promptprompt) # 6. 使用链进行解析 raw_test_case 用例ID: TC_LOGIN_001 用例标题: 用户登录成功测试 前置条件: 用户已注册 测试步骤: 1. 请求登录接口 /api/v1/auth/login 2. 请求方法: POST 3. 请求头: Content-Type: application/json 4. 请求体: {username: test_user, password: Test123456} 预期结果: 1. 状态码: 200 2. 响应体包含: code0, message登录成功, 并且data字段中有token和user_id。 try: # 运行链 parsing_result parsing_chain.run(test_case_textraw_test_case) # 使用解析器将LLM的文本输出转为TestCaseInfo对象 test_case_info: TestCaseInfo parser.parse(parsing_result) print(f解析成功接口名: {test_case_info.api_name}) print(f请求参数: {test_case_info.request_params}) except Exception as e: print(f解析失败: {e})实操要点temperature参数在这里设置为较低值0.1-0.3是为了让模型在解析这种结构化任务时输出更一致。partial_variables的使用是关键它动态地将parser生成的格式说明一段描述TestCaseInfo各字段的文本插入到提示词中指导模型输出。3.3 构建测试代码生成链拿到结构化的TestCaseInfo后下一步就是将其转化为可执行的代码。我们需要另一个链和提示词。from langchain.chains import TransformChain import json # 1. 定义代码生成提示词模板 code_gen_prompt_template 你是一个Python测试代码专家擅长使用pytest和requests编写简洁、健壮的接口测试。 请根据以下结构化的测试信息生成一个完整的pytest测试函数。 **团队编码规范要求** 1. 函数名必须以test_开头描述清晰。 2. 使用requests库发起HTTP请求。 3. 使用pytest的assert进行断言。 4. 对请求参数和预期结果使用明确的变量。 5. 添加必要的注释。 **测试信息** json {test_case_info_json}请生成单个pytest测试函数代码。只输出代码块不要有任何其他解释。 code_gen_prompt PromptTemplate( templatecode_gen_prompt_template, input_variables[test_case_info_json] )2. 创建代码生成链code_gen_chain LLMChain(llmllm, promptcode_gen_prompt)3. 将上一个链的输出TestCaseInfo对象转换为JSON字符串作为下一个链的输入def transform_parsed_to_input(inputs: dict) - dict: # inputs 来自上一个链的输出key为 text parsed_info: TestCaseInfo parser.parse(inputs[text]) return {test_case_info_json: parsed_info.json(indent2)}transform_chain TransformChain( input_variables[text], output_variables[test_case_info_json], transformtransform_parsed_to_input )4. 使用SequentialChain串联两个链from langchain.chains import SequentialChain overall_chain SequentialChain( chains[parsing_chain, transform_chain, code_gen_chain], input_variables[test_case_text], output_variables[text], # 注意这里最终输出是code_gen_chain的“text” verboseTrue # 调试时可开启查看链间传递 )5. 执行完整流程final_result overall_chain.run(test_case_textraw_test_case) print(生成的测试代码) print(final_result)运行上述代码你可能会得到类似下面的输出 python import pytest import requests def test_user_login_success(): TC_LOGIN_001: 用户登录成功测试 前置条件: 用户已注册 # 接口信息 url http://your-api-server.com/api/v1/auth/login # TODO: 替换为实际基础URL或使用fixture method POST headers {Content-Type: application/json} payload {username: test_user, password: Test123456} # 发起请求 response requests.request(methodmethod, urlurl, headersheaders, jsonpayload) # 断言状态码 assert response.status_code 200, f预期状态码200实际为{response.status_code} # 断言响应体结构 response_json response.json() assert response_json.get(code) 0, f预期code为0实际为{response_json.get(code)} assert response_json.get(message) 登录成功, f预期message为登录成功实际为{response_json.get(message)} assert data in response_json, 响应体中缺少data字段 assert token in response_json[data], data字段中缺少token assert user_id in response_json[data], data字段中缺少user_id assert isinstance(response_json[data][user_id], (int, str)), user_id应为数字或字符串类型避坑指南模型生成的代码中URL往往是硬编码的。在实际工具中我们需要在提示词里加入“请使用base_url变量”或“从环境变量读取主机地址”的指引或者在后处理步骤中用正则表达式进行替换。更好的做法是将base_url作为生成链的另一个输入变量。3.4 代码后处理与格式化模型直接生成的代码可能在某些细节上不完美我们需要一个后处理环节。import subprocess import tempfile import os def post_process_and_format(code_str: str, base_url: str None) - str: 对生成的代码进行后处理和格式化。 # 1. 替换硬编码的URL简单示例实际可用更复杂的模板或正则 if base_url: # 这是一个简单的替换假设模型生成的代码中有一行 url http://... import re # 匹配 url 开头的赋值语句 pattern r(url\s*\s*)[^]*() code_str re.sub(pattern, rf\1{base_url}\2, code_str, count1) # 2. 使用black格式化代码 try: with tempfile.NamedTemporaryFile(modew, suffix.py, deleteFalse) as tmp: tmp.write(code_str) tmp_path tmp.name # 调用black格式化设置行长度等 result subprocess.run( [black, --line-length, 88, --quiet, tmp_path], capture_outputTrue, textTrue ) if result.returncode 0: with open(tmp_path, r) as f: formatted_code f.read() else: print(fBlack格式化失败: {result.stderr}) formatted_code code_str except Exception as e: print(f格式化过程异常: {e}) formatted_code code_str finally: if os.path.exists(tmp_path): os.unlink(tmp_path) # 3. (可选) 使用isort整理import顺序 # ... return formatted_code # 使用后处理函数 formatted_code post_process_and_format(final_result, base_urlhttps://api.example.com) print(格式化后的代码) print(formatted_code)3.5 集成与API服务封装FastAPI最后我们将整个流程封装成一个Web服务提供RESTful API。from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional import logging logging.basicConfig(levellogging.INFO) app FastAPI(title测试用例自动化生成工具) class TestCaseConversionRequest(BaseModel): test_case_text: str base_url: Optional[str] http://localhost:8080 framework: Optional[str] pytest # 可扩展支持 unittest, httpx等 class TestCaseConversionResponse(BaseModel): success: bool generated_code: Optional[str] None error_message: Optional[str] None app.post(/convert, response_modelTestCaseConversionResponse) async def convert_test_case(request: TestCaseConversionRequest): 将手工测试用例文本转换为接口自动化测试代码。 try: logging.info(f收到转换请求用例长度{len(request.test_case_text)}) # 1. 调用我们之前构建的SequentialChain raw_generated_code overall_chain.run(test_case_textrequest.test_case_text) # 2. 后处理与格式化 final_code post_process_and_format(raw_generated_code, base_urlrequest.base_url) return TestCaseConversionResponse( successTrue, generated_codefinal_code ) except Exception as e: logging.error(f转换过程发生错误: {e}, exc_infoTrue) raise HTTPException(status_code500, detailf内部处理错误: {str(e)}) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)现在你可以通过向http://localhost:8000/convert发送一个POST请求JSON体包含test_case_text即可获得生成的测试代码。这可以轻松集成到你的测试管理平台或CI/CD流水线中。4. 高级优化与生产级考量4.1 利用RAG增强生成准确性如果测试用例描述比较简略例如只写了“测试登录失败”或者需要遵循特定的、复杂的接口协议我们可以引入RAG检索增强生成来提供更多上下文。场景你的团队有一个在线的接口文档站点Swagger/OpenAPI或内部的API规范文档。我们可以将这些文档切片并存入向量数据库。步骤知识库构建使用UnstructuredMarkdownLoader或直接解析OpenAPI JSON文件加载接口文档。用文本分割器切分后通过嵌入模型如text-embedding-3-small转换为向量存入Chroma。检索集成在代码生成链的提示词中加入检索到的相关文档片段。# 伪代码示例 retriever vectorstore.as_retriever(search_kwargs{k: 3}) # 检索最相关的3个片段 relevant_docs retriever.get_relevant_documents(queryf{test_case_info.api_name} {test_case_info.endpoint}) context_from_docs \n.join([doc.page_content for doc in relevant_docs]) # 将 context_from_docs 作为变量加入到代码生成提示词中提示词优化修改代码生成提示词开头加入“以下是相关接口文档的参考信息[context_from_docs]。请严格参照此规范编写测试代码。”这样模型在生成代码时就能“看到”接口的详细参数说明、错误码定义等极大提升生成代码的准确性和规范性。4.2 通过LangGraph实现自修正工作流对于更复杂的场景我们可以利用LangGraph构建一个带反馈循环的图。基本思路是生成代码 - 尝试用语法检查或简单执行在沙箱中- 如果失败将错误信息反馈给模型 - 模型修正代码 - 再次检查直到成功或达到最大重试次数。from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated import operator class GraphState(TypedDict): test_case_info: TestCaseInfo generated_code: str verification_result: str # 存放语法检查或执行结果 attempts: int def generate_code(state: GraphState): 节点生成代码 # 调用之前的代码生成链 code code_gen_chain.run(test_case_info_jsonstate[test_case_info].json()) return {generated_code: code} def verify_code(state: GraphState): 节点验证代码例如用ast进行语法检查 import ast code state[generated_code] try: ast.parse(code) return {verification_result: 语法检查通过} except SyntaxError as e: return {verification_result: f语法错误: {e}} def should_retry(state: GraphState) - str: 条件边判断是否需要重试 if 语法错误 in state[verification_result] and state[attempts] 3: return retry else: return end def create_workflow(): workflow StateGraph(GraphState) workflow.add_node(generate, generate_code) workflow.add_node(verify, verify_code) workflow.set_entry_point(generate) workflow.add_edge(generate, verify) # 根据验证结果决定流向 workflow.add_conditional_edges( verify, should_retry, { retry: generate, # 失败则返回generate节点重试 end: END } ) return workflow.compile() # 使用图 graph create_workflow() initial_state {test_case_info: test_case_info, generated_code: , verification_result: , attempts: 0} final_state graph.invoke(initial_state)这个模式虽然更复杂但能显著提升生成代码的可靠性和健壮性是走向生产级应用的重要一步。4.3 模板化与风格定制不同的团队有不同的测试框架和编码风格。我们可以将“代码生成”这一步模板化。创建模板库为pytestrequests、pytesthttpx、unittest等不同风格创建不同的提示词模板文件.txt或.jinja2。动态加载根据用户请求中的framework字段加载对应的提示词模板。变量注入使用Jinja2等模板引擎将TestCaseInfo中的字段如endpoint,request_params注入到模板中形成最终的提示词。这样工具就具备了良好的可扩展性能适应不同团队的技术栈。5. 常见问题、挑战与实战心得在实际开发和试用过程中我遇到了不少典型问题以下是总结和解决方案。5.1 模型生成结果不稳定问题同样的输入偶尔会生成格式错误如不完整的JSON或风格迥异的代码。解决降低Temperature如之前所述在解析和生成任务中将temperature设为较低值0.1-0.3。强化提示词约束在提示词中使用“必须”、“严格遵循”、“请只输出”等强引导性词语并明确禁止的行为。使用结构化输出PydanticOutputParser是解决解析不稳定的利器它能强制模型输出特定格式。后处理校验对生成的代码进行基础的语法树AST解析如果失败可以触发重试或返回友好错误。5.2 生成的代码缺乏实际可运行性问题代码看起来正确但缺少必要的依赖导入、测试数据如动态token、环境配置或断言过于简单。解决提供更丰富的上下文在提示词中嵌入一段“黄金范例”展示一个包含完整导入、夹具使用、数据准备和复杂断言的测试函数。分步生成不要指望一个提示词生成完美代码。可以设计多步链第一步生成测试函数主体第二步根据主体生成对应的conftest.py中的共享夹具第三步生成测试数据工厂或工具函数。集成团队代码片段库利用RAG在生成时检索团队历史优秀测试代码片段作为参考。5.3 处理复杂业务逻辑用例力不从心问题对于涉及多步骤、状态转移的流程性测试用例如“下单-支付-查询订单”模型可能难以生成连贯的、有状态依赖的测试脚本。解决用例拆分在输入前先让人工或用一个简单的规则引擎将复杂的端到端用例拆分成多个独立的原子接口用例。工具分别生成再由人工或脚本组装。引入Graph思维使用LangGraph来显式地建模测试流程。每个接口调用是一个节点节点间的状态如登录后的token可以传递。这需要更高级的设计但能处理复杂场景。定位为辅助而非替代明确工具边界。它最擅长生成原子接口的测试脚本。对于复杂的业务流测试将其作为生成“积木”的工具由测试工程师来设计和搭建“建筑”。5.4 成本与性能考量问题大量用例转换时API调用成本和高耗时成为瓶颈。解决模型选型在保证质量的前提下选用更经济高效的模型。例如对于格式规整的用例解析可以使用较小的模型如Qwen-Turbo对于代码生成再用更强大的模型。批量处理与异步设计批量转换接口并使用异步调用asyncio来并发处理多个用例提升吞吐量。缓存机制对相同的或高度相似的测试用例输入可以缓存转换结果避免重复调用LLM。离线与微调对于极其固定的用例格式和代码风格可以考虑收集一批高质量的“输入-输出”配对数据对中小模型进行微调SFT得到一个专有的、成本更低的转换模型。5.5 集成到现有工作流问题生成的代码如何无缝融入现有的自动化测试框架和CI/CD解决标准化输出确保工具生成的代码文件结构、命名规则如test_*.py符合现有pytest收集规则。提供适配器工具不仅可以输出代码文件还可以输出符合pytest-xdist执行的测试列表或者生成Jenkinsfile/GitLab CI的配置片段。API化正如我们上面用FastAPI做的那样提供标准的REST API方便与Jira、TestRail、Zentao禅道等测试管理工具集成实现“一键生成并提交到Git仓库”。经过几个项目的实践我的核心体会是这个工具的价值不在于实现100%的全自动替换而在于成为一个强大的“测试开发助手”。它能处理掉80%的模板化、重复性编码工作让测试工程师能够聚焦在那20%需要人类智慧和经验的复杂逻辑设计、异常场景挖掘和测试策略优化上。从手工用例到自动化脚本的转化率提升是立竿见影的尤其是在回归测试套件的建设和维护中它能显著降低技术债务让团队更有信心和动力去推进自动化测试。