打造全方位接口自动化测试平台:实现DeepSeek智能助手

目前正在着手开发一款功能全面的接口自动化测试平台,该平台将涵盖登录、用户管理、权限控制、项目管理、用例管理、测试数据管理、测试报告生成、任务调度、缺陷跟踪以及系统配置等多个核心模块。随着智能体的爆火,准备开发一款测试工具智能助手,可以帮助执行任务、生成数据、分析测试结果等。因为智能助手的使用需要依赖平台的数据,所以本篇内容主要讲智能助手的搭建过程。具体的智能助手的操作逻辑,需要等平台主要功能完成后实现。

在这里插入图片描述

技术选型

  • 后端:采用Java作为主要开发语言。
  • 前端:使用VUE框架进行界面展示。
  • 智能助手:使用DeepSeekFunction Calling,将函数传送给AI大模型,AI判断是否需要使用函数执行。

定义实体

DeepSeekFunction Calling,在调用模型接口时,需要将自定义的函数,以List方式传给模型,然后模型返回用户的消息是否需要使用Function Calling。函数列表的格式如下:

tools = [
    {
    
    
        "type": "function",
        "function": {
    
    
            "name": "get_weather",
            "description": "Get weather of an location, the user shoud supply a location first",
            "parameters": {
    
    
                "type": "object",
                "properties": {
    
    
                    "location": {
    
    
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"]
            },
        }
    },
]

我们根据上面的格式,定义自己的实体类:

@Data
public class Tool {
    
    
    String type;
    Function function;
}

@Data
public class Function {
    
    
    String name;
    String description;
    Parameters parameters;

}

@Data
public class Parameters {
    
    
    String type;
    Properties properties;
    List<String> required;

}

@Data
public class Properties {
    
    
    Location location;
}

@Data
public class Location {
    
    
    String type;
    String description;
}

定义回调函数格式

public class Tools {
    
    

    private static Tool createTool(ToolParameter toolParameterDTO) {
    
    
        Location location = new Location();
        location.setDescription(toolParameterDTO.getLocalDesc());
        location.setType("string");
        Properties properties = new Properties();
        properties.setLocation(location);
        Parameters parameters = new Parameters();
        parameters.setType("object");
        parameters.setProperties(properties);
        parameters.setRequired(toolParameterDTO.getRequired());
        Function function = new Function();
        function.setName(toolParameterDTO.getName());
        function.setDescription(toolParameterDTO.getFuncDesc());
        function.setParameters(parameters);
        Tool tool = new Tool();
        tool.setType("function");
        tool.setFunction(function);
        return tool;
    }

    private static Tool runTestJob() {
    
    
        ToolParameter runTestJobTool = new ToolParameter();
        runTestJobTool.setName("runTestJob");
        runTestJobTool.setFuncDesc("执行接口测试任务,运行接口测试任务");
        runTestJobTool.setLocalDesc("任务名称,例如:订单模块测试任务、用户中心测试任务");
        runTestJobTool.setRequired(Collections.singletonList("job"));
        return createTool(runTestJobTool);
    }

    private static Tool findTestReport() {
    
    
        ToolParameter findTestReport = new ToolParameter();
        findTestReport.setName("findTestReport");
        findTestReport.setFuncDesc("查询测试任务的测试报告");
        findTestReport.setLocalDesc("任务名称,例如:订单模块测试任务、用户中心测试任务");
        findTestReport.setRequired(Collections.singletonList("job"));
        return createTool(findTestReport);
    }


    public static List<Tool> getTools() throws InvocationTargetException, IllegalAccessException {
    
    
        List<Tool> tools = new ArrayList<>();
        Method[] methods = Tools.class.getDeclaredMethods();
        for (Method method: methods) {
    
    
            if (!method.getName().equals("createTool") && method.getReturnType().equals(Tool.class)) {
    
    
                method.setAccessible(true);
                tools.add((Tool) method.invoke(null));
            }
        }
        return tools;
    }

}

示例中,通过调用createTool来定义回调函数,并通过getTools()将所有定义的回调函数返回。

实现自定义函数逻辑

public class Functions {
    
    

    public static String runTestJob(String string){
    
    
        return "执行任务:" + string;
    }

    public static String findTestReport(String string) {
    
    
        return "查找测试报告:" + string;
    }

    public static Map<String, Object> methodMap(String functionName, String params) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("runTestJob", runTestJob(params));
        map.put("findTestReport", findTestReport(params));
        return map;
    }

}

由于需要使用平台数据,目前平台功能还没完全实现,所以具体实现逻辑省略。

发送消息给AI

由于我项目用的JDK8+SpringBoot2.x,SpringAI支持JDK17+和SpringBoot3.2以上,所以只能用okhttp自己封装请求接口了。

@Component
public class DeepSeek {
    
    

    @Value("${deepseek.api-key}")
    private String apiKey;

    @Value("${deepseek.base-url}")
    private String url;


    public Map<String, Object> sendMessage(String message) throws InvocationTargetException, IllegalAccessException, IOException {
    
    
        Map<String, Object> msg = new HashMap<>();
        msg.put("role", "user");
        msg.put("content", message);
        Map<String, Object> bodys = new HashMap<>();
        bodys.put("messages", Collections.singletonList(msg));
        bodys.put("model", "deepseek-chat");
        bodys.put("tools", Tools.getTools());

        Request.Builder build = new Request.Builder().url(url);
        Request request;
        OkHttpClient okHttpClient = new OkHttpClient().newBuilder().connectTimeout(60, TimeUnit.SECONDS).build();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, JSONObject.toJSONString(bodys));
        build.addHeader("Content-Type", "application/json");
        build.addHeader("Authorization", "Bearer " + apiKey);
        request = build.method("POST", body).build();
        Map<String, Object> result_message = new HashMap<>();
        try {
    
    
            String response = Objects.requireNonNull(okHttpClient.newCall(request).execute().body()).string();
            JSONObject res = JSONObject.parseObject(response);
            JSONArray resMsg = res.getJSONArray("choices");
            JSONObject result_msg = resMsg.getJSONObject(0).getJSONObject("message");
            if (result_msg.containsKey("tool_calls")) {
    
    
                JSONObject tool = result_msg.getJSONArray("tool_calls").getJSONObject(0);
                String functionName = tool.getJSONObject("function").getString("name");
                String arguments = JSONObject.parseObject(tool.getJSONObject("function").getString("arguments")).getString("location");
                String result = (String) Functions.methodMap(functionName, arguments).get(functionName);
                result_message.put("message", result);
                result_message.put("type", 0);
                return result_message;
            }else {
    
    
                String content = result_msg.getString("content");
                result_message.put("message", content);
                result_message.put("type", 1);
                return result_message;
            }
        }catch (Exception e) {
    
    
            result_message.put("message", e.getMessage());
            result_message.put("type", 2);
            return result_message;
        }

    }

}

结果展示

运行后,使用PostMan调用,查看返回结果。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述