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需持续调优,测试用例的补全、最终用例的存储、下载功能有待开发,敬请期待~

相关推荐
m0_603888711 小时前
FineInstructions Scaling Synthetic Instructions to Pre-Training Scale
人工智能·深度学习·机器学习·ai·论文速览
爬台阶的蚂蚁1 小时前
RAG概念和使用
ai·rag
undsky_1 小时前
【RuoYi-SpringBoot3-Pro】:将 AI 编程融入传统 java 开发
java·人工智能·spring boot·ai·ai编程
AI应用开发实战派1 小时前
AI人工智能中Bard的智能电子商务优化
人工智能·ai·bard
AI原生应用开发2 小时前
AIGC领域Bard在通信领域的内容创作
ai·aigc·bard
唐诺2 小时前
深入了解AI
人工智能·ai
ZEGO即构开发者2 小时前
如何用一句话让AI集成 ZEGO 产品
ai·实时互动·实时音视频·rtc
阿杰学AI2 小时前
AI核心知识76——大语言模型之RAG 2.0(简洁且通俗易懂版)
人工智能·ai·语言模型·自然语言处理·rag·检索增强生成·rag2.0
GuoDongOrange3 小时前
智能体来了从 0 到 1:工作流在智能体系统中的真实作用
ai·智能体·从0到1·智能体来了·智能体来了从0到1
爱吃涮肉3 小时前
# 第二章:ClaudeCode核心功能(详细版)
ai