Spring AI Alibaba智能测试用例生成

👋 还在为手动编写测试用例秃头吗?面对复杂需求文档,一个一个抠测试点,不仅效率低,还容易遗漏关键场景?为了解决该问题,笔者依托Spring AI开发了测试用例生成工具~
后端源码:https://github.com/qa-dpp/case-ai-backend
前端源码:https://github.com/qa-dpp/case-ai-front
🌟 核心功能

  • 全类型文档分析:基于 Spring AI Alibaba的自然语言处理和图像识别能力,支持pdf 、word、txt多种格式需求文档,精准提取需求点。
  • 智能生成与筛选:采用 Spring AI Alibaba的生成者评估者模式,生成者批量产出候选测试用例,评估者从合理性、覆盖率等维度筛选,保障用例质量与全面性。
  • 脑图可视化呈现:将生成的测试用例自动转化为表格和脑图,清晰展示用例逻辑结构和与需求的对应关系,方便理解、执行与团队协作。
    🛠️ 技术架构
  • 底层框架:依托 Spring AI Alibaba,实现对文档的智能解析与处理。
  • 生成模式:运用生成者评估者模式,构建高效可靠的测试用例生成流程。
  • 可视化:集成表格和脑图生成技术,让测试用例以直观图形化方式呈现。

🛠️代码整体框架:

🛠️代码详解:

1. 常量定义(Consts.java)

复制代码
package com.fingertip.caseaibackend.commons;

public class Consts {
    public static final String ANALYZE_PROMPT = ...;
    public static final String CASE_WRITER_PROMPT = ...;
    public static final String CASE_REVIEWER_PROMPT = ...;
    public static final String CASE_FORMAT_PROMPT = ...; public static final String VISUAL_PROMPT = ...; public static final String ORIGIN_MESSAGE = "originMessage"; public static final String CASE_INFO_MESSAGE = "caseInfoMessage"; public static final String CASE_REVIEW_MESSAGE = "caseReviewMessage"; public static final String CASE_FORMAT_MESSAGE = "caseFormatMessage"; }

该类定义了一系列常量,包括与大语言模型交互时使用的提示信息(如需求分析提示、测试用例编写提示等)以及状态图中使用的消息键。

2. 配置文件(application.yml)

复制代码
server:
  port: 8080

spring:
  application:
    name: spring-ai-alibaba-openai-chat-model-example
  ai:
    openai:
      api-key: xxx
      base-url: https://api.siliconflow.cn/
      chat:
        options:
          model: deepseek-ai/DeepSeek-R1 servlet: multipart: max-file-size: 50MB max-request-size: 50MB case-model: visual: api-key: xxx base-url: https://api.siliconflow.cn/ model: Qwen/Qwen2.5-VL-72B-Instruct analyze: api-key: xxx base-url: https://api.siliconflow.cn/ model: deepseek-ai/DeepSeek-R1 generate: api-key: xxx base-url: https://api.siliconflow.cn/ model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B reviewer: api-key: xxx base-url: https://api.siliconflow.cn/ model: MiniMaxAI/MiniMax-M1-80k format: api-key: xxx base-url: https://api.siliconflow.cn/ model: deepseek-ai/DeepSeek-V3 logging: level: root: INFO org.springframework.ai: DEBUG com.example.demo: DEBUG

配置文件定义了服务器端口、Spring AI 的基本配置(如 API 密钥、基础 URL、模型类型)以及不同任务(如可视化、需求分析、测试用例生成等)使用的模型信息。同时,还配置了文件上传的最大大小和日志级别。

3. 大语言模型配置(LLMConfig.java)

复制代码
package com.fingertip.caseaibackend.aiproxies.configs;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LLMConfig { @Value("${case-model.analyze.api-key}") private String analyze_apiKey = ""; @Value("${case-model.analyze.base-url}") private String analyze_baseUrl = ""; @Value("${case-model.analyze.model}") private String analyze_Model = ""; @Bean public ChatModel analyzeModel() { OpenAiChatOptions options = new OpenAiChatOptions(); options.setModel(analyze_Model); options.setTemperature(0.7); return OpenAiChatModel.builder().openAiApi(OpenAiApi.builder().apiKey(analyze_apiKey).baseUrl(analyze_baseUrl).build()).defaultOptions(options).build(); } // 其他模型配置... }

该类使用 Spring 的 @Configuration 注解,通过 @Value 注解从配置文件中读取不同任务所需的 API 密钥、基础 URL 和模型类型,然后创建相应的 ChatModel 实例。

4. 控制器(AiChatController.java)

复制代码
@RestController
@RequestMapping("/ai-api")
public class AiChatController {
    private static final String DEFAULT_PROMPT = "你好,介绍下你自己!";

    private final ChatClient openAiAnalyzeChatClient;
    private final ChatClient openAiGenerateChatClient; private final ChatClient openAiReviewerChatClient; private final ChatClient openAiFormatChatClient; private final ChatClient openAiVisualChatClient; public AiChatController(@Qualifier("analyzeModel") ChatModel analyzeModel, ...) { this.openAiAnalyzeChatClient = ChatClient.builder(analyzeModel) .defaultAdvisors(new SimpleLoggerAdvisor()) .defaultOptions(OpenAiChatOptions.builder().topP(0.7).build()) .build(); // 其他 ChatClient 初始化...  } @PostMapping("/file/upload") public ApiResult<String> uploadFile(@RequestParam("files") MultipartFile[] files) { ApiResult<String> result = new ApiResult<>(); try { if (files == null || files.length == 0) { result.setMessage("上传文件为空"); result.setCode(400); return result; } StringBuilder contentBuilder = new StringBuilder(); for (MultipartFile file : files) { if (file.isEmpty()) { continue; } String fileName = file.getOriginalFilename(); if (fileName == null) { continue; } if (fileName.endsWith(".docx") || fileName.endsWith(".pdf")) { List<Media> mediaList = null; if (fileName.endsWith(".docx")) { // 将 docx 转换为 PDF XWPFDocument docxDoc = new XWPFDocument(file.getInputStream()); ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); PdfOptions options = PdfOptions.create(); PdfConverter.getInstance().convert(docxDoc, pdfOutputStream, options); byte[] pdfBytes = pdfOutputStream.toByteArray(); mediaList = convertPdfToImages(pdfBytes); } else if (fileName.endsWith(".pdf")) { mediaList = convertPdfToImages(file.getBytes()); } if (mediaList != null && !mediaList.isEmpty()) { UserMessage message = UserMessage.builder().text(Consts.VISUAL_PROMPT).media(mediaList).metadata(new HashMap<>()).build(); message.getMetadata().put(MESSAGE_FORMAT, MessageFormat.IMAGE); String content = openAiVisualChatClient.prompt(new Prompt(message)).call().content(); contentBuilder.append(content).append("\n"); } } else { if (!file.isEmpty()) { contentBuilder.append(file.getOriginalFilename()).append(":\n"); contentBuilder.append(new String(file.getBytes(), StandardCharsets.UTF_8)).append("\n"); } } } String content = contentBuilder.toString(); String resp = openAiAnalyzeChatClient .prompt(Consts.ANALYZE_PROMPT) .user(content) .call() .content(); result.setData(resp); result.setMessage("解析完成"); result.setCode(200); } catch (Exception e) { result.setMessage("文件处理异常: " + e.getMessage()); result.setCode(500); return result; } return result; } // 其他接口方法... }

AiChatController 是项目的控制器类,负责处理客户端的请求。它通过构造函数注入不同任务的 ChatClient 实例,提供了文件上传、流式文件上传和测试用例创建等接口。在文件上传接口中,会根据文件类型(.docx.pdf)进行相应的处理,如将 docx 转换为 PDF,再将 PDF 转换为图片,最后调用可视化模型进行处理

5. 节点类(CaseGenerateNode.java, CaseReviewerNode.java, CaseFormatNode.java)

复制代码
package com.fingertip.caseaibackend.aiproxies.nodes;

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.fingertip.caseaibackend.commons.Consts;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.Map; public class CaseGenerateNode implements NodeAction { private final ChatClient chatClient; public CaseGenerateNode(ChatClient chatClient) { this.chatClient = chatClient; } @Override public Map<String, Object> apply(OverAllState t) { String origin_message = (String) t.value(Consts.ORIGIN_MESSAGE).orElse(""); String case_reviewer_message = (String) t.value(Consts.CASE_REVIEW_MESSAGE).orElse(""); String caseInfo = (String) t.value(Consts.CASE_INFO_MESSAGE).orElse(""); if (!StringUtils.hasText(origin_message)) { throw new IllegalArgumentException("没有找到原始消息"); } String content = Consts.CASE_WRITER_PROMPT + "\n\n" + origin_message; if (StringUtils.hasText(case_reviewer_message) && StringUtils.hasText(caseInfo)) { content = "%s\n# 原始需求:\n%s\n\n# 上个版本需求用例:\n%s \n# 专家意见:%s\n".formatted(Consts.CASE_WRITER_PROMPT, origin_message, caseInfo, case_reviewer_message); } ChatResponse response = chatClient.prompt(content).call().chatResponse(); String output = null; if (response != null) { output = response.getResult().getOutput().getText(); } Map<String, Object> updated = new HashMap<>(); updated.put(Consts.CASE_INFO_MESSAGE, output); return updated; } }

这些节点类实现了 NodeAction 接口,用于在状态图中执行特定的任务。例如,CaseGenerateNode 负责根据原始需求和评审意见生成测试用例,它从 OverAllState 中获取所需的信息,构造请求内容,调用相应的 ChatClient 与大语言模型交互,最后将生成的测试用例信息存储到 OverAllState 中。

6. 反馈分发器(FeedbackDispatcher.java)

复制代码
package com.fingertip.caseaibackend.aiproxies.nodes;

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.EdgeAction;
import com.fingertip.caseaibackend.commons.Consts;

public class FeedbackDispatcher implements EdgeAction {
    @Override
    public String apply(OverAllState t) { String output = (String) t.value(Consts.CASE_REVIEW_MESSAGE).orElse(""); return output.toLowerCase().contains("approve") ? "positive" : "negative"; } }

FeedbackDispatcher 实现了 EdgeAction 接口,根据测试用例评审结果(CASE_REVIEW_MESSAGE)判断是否通过评审,并返回相应的反馈结果(positivenegative),用于状态图中的流程控制

🌟C端页面

总结:

目前用例生成的promot需持续调优,测试用例的补全、最终用例的存储、下载功能有待开发,敬请期待~

相关推荐
LilySesy11 小时前
【案例总结】幽灵单据——消失的交货单号
数据库·ai·oracle·编辑器·sap·abap
你可以叫我仔哥呀11 小时前
Java程序员学从0学AI(七)
java·开发语言·人工智能·ai·spring ai
DM今天肝到几点?13 小时前
【7.26-7.28胜算云AI日报:首个开源3D世界生成模型腾讯混元、微软预示 8 月 GPT-5 发布、Nemotron推理、商汤悟能、DM夺金】
人工智能·vscode·microsoft·3d·ai·chatgpt
GEM的左耳返1 天前
互联网大厂Java面试:微服务与AI技术深度交锋
spring cloud·ai·微服务架构·java面试·rag技术
姜 萌@cnblogs1 天前
Saga Reader 0.9.9 版本亮点:深入解析核心新功能实现
前端·ai·rust
奋进的孤狼2 天前
【Spring AI】阿里云DashScope灵积模型
人工智能·spring·阿里云·ai·云计算
哥不是小萝莉2 天前
CocoIndex实现AI数据语义检索
ai·cocoindex
charlee442 天前
PandasAI连接LLM进行智能数据分析
ai·数据分析·llm·pandasai·deepseek
九河云2 天前
从 “制造” 到 “智造”:中国制造业数字化转型的突围之路
科技·ai·制造·数字化转型·传统
yeshan3332 天前
使用 Claude Code 的自定义 Sub Agent 完善博文写作体验
ai·github·agent·claudecode