Junit4遇上chatGPT
共 11243字,需浏览 23分钟
·
2024-06-07 09:10
这是一篇适合Java工程师体质的AI开发教程。
本教程会教你写一个简单的junit4的Rule,该Rule在基于junit4的测试方法失败后,自动向GPT发送错误信息并通过GPT分析得出代码修改建议。
首先向AI问好
简单的通过AI,让它基于json格式生成相应的类文件,这样方便我们在代码中使用,而不用显示写json字符串。
package com.jd.jr.parrot.llm;import lombok.Data;/*** created on 2023/7/25 20:07** @author Dragonchu*/@Datapublic class Choice {private String finish_reason;private int index;private Message message;}
package com.jd.jr.parrot.llm;import lombok.Data;/*** created on 2023/7/25 20:07** @author Dragonchu*/@Datapublic class Message {private String role;private String content;}
package com.jd.jr.parrot.llm;import lombok.Data;/*** created on 2023/7/25 20:06** @author Dragonchu*/@Datapublic class Usage {private int completion_tokens;private int prompt_tokens;private int total_tokens;}
package com.jd.jr.parrot.llm;import java.util.List;import lombok.Data;/*** created on 2023/7/25 20:17** @author Dragonchu*/@Datapublic class OpenAiRequest {private String erp;private List<Message> messages;private String model;}
package com.jd.jr.parrot.llm;import java.util.List;import lombok.Data;/*** created on 2023/7/25 20:04** @author Dragonchu*/@Datapublic class OpenAiResponse {private long created;private Usage usage;private String model;private String id;private List<Choice> choices;private String object;}
封装一个简单的工具,以供使用GPT
package com.jd.jr.parrot.llm;import com.google.gson.Gson;import java.util.Arrays;import lombok.extern.slf4j.Slf4j;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;import java.io.IOException;import java.nio.charset.Charset;/*** created on 2023/7/25 20:07** @author Dragonchu*/@Slf4jpublic class LLMUtil {private static String url = "Your url";private static String apiKey = "Your key";public static String sendMsg(String msg){OpenAiRequest request = new OpenAiRequest();Message message = new Message();message.setRole("user");message.setContent(msg);request.setMessages(Arrays.asList(message));request.setModel("gpt-3.5-turbo");Gson gson = new Gson();String result = postGPT(url, gson.toJson(request));OpenAiResponse openAiResponse = gson.fromJson(result, OpenAiResponse.class);return openAiResponse.getChoices().get(0).getMessage().getContent();}/*** 发送 post 请求** @param url 请求地址* @param jsonStr Form表单json字符串* @return 请求结果*/public static String postGPT(String url, String jsonStr) {// 创建httpClientCloseableHttpClient httpClient = HttpClients.createDefault();// 创建post请求方式实例HttpPost httpPost = new HttpPost(url);httpPost.setHeader("Content-type", "application/json;charset=utf-8");httpPost.setHeader("Authorization", apiKey);// 设置参数---设置消息实体 也就是携带的数据StringEntity entity = new StringEntity(jsonStr, Charset.forName("UTF-8"));// 设置编码格式entity.setContentEncoding("UTF-8");// 发送Json格式的数据请求entity.setContentType("application/json");// 把请求消息实体塞进去httpPost.setEntity(entity);// 执行http的post请求CloseableHttpResponse httpResponse;String result = null;try {httpResponse = httpClient.execute(httpPost);result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");if (httpResponse.getStatusLine().getStatusCode() != 200) {System.out.println("post->return error,result: " + result);log.error("post->return error,result: {}", result);}} catch (IOException e) {log.error("postStream->error", e);e.printStackTrace();}return result;}public static void main(String[] args) {log.info(sendMsg("Who are you?"));}}
然后写一个Watcher
junit4提供了Rule机制,该机制利用AOP,使得我们可以在测试代码中嵌入相应的逻辑,比如在执行代码前输出方法名,捕获异常等。TestWatcher是一个抽象类,通过覆盖该类我们可以得到一个满足我们需求的Rule。
我们希望能在测试方法失败抛出异常后,直接将异常信息和异常堆栈发送给GPT,同时,也要将出错的代码文件发送给GPT(注意敏感信息哦,本文只是一个教程,请勿私自将敏感代码发送给GPT)。
简单写一个工具类,这个类封装了从异常堆栈中获取文件的能力。
package com.jd.jr.parrot;import java.io.BufferedReader;import java.io.File;import java.io.FileReader;import java.io.IOException;import java.nio.file.Path;import java.nio.file.Paths;import java.util.regex.Matcher;import java.util.regex.Pattern;/*** created on 2023/7/28 15:08** @author Dragonchu*/public class ExceptionUtil {public static String getFileName(String stackTraceLine) {// 使用正则表达式提取文件名Pattern pattern = Pattern.compile("\\((.*\\.java)");Matcher matcher = pattern.matcher(stackTraceLine);if (matcher.find()) {String fileName = matcher.group(1);return fileName;}return null;}public static Path getPath(String stackTraceLine) {// 提取文件路径int firstParenthesisIndex = stackTraceLine.indexOf("(");int firstDotIndex = stackTraceLine.lastIndexOf(".", firstParenthesisIndex);String packagePath = stackTraceLine.substring(0, firstDotIndex).replace(".", File.separator);// 获取根路径String rootPath = new File("").getAbsolutePath();Path filePath = Paths.get(rootPath, "src", "main", "java", packagePath + ".java");return filePath;}public static String getCodeFile(String stackTraceLine) {Path filePath = getPath(stackTraceLine);if (filePath.toFile().exists()) {// 读取文件内容try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) {StringBuilder content = new StringBuilder();String line;while ((line = reader.readLine()) != null) {content.append(line).append("\n");}return content.toString();} catch (IOException e) {e.printStackTrace();}}return null;}public static void main(String[] args) {String stackTraceLine = "com.jd.jr.parrot.ExceptionExample.main(ExceptionExample.java:24)";String fileName = getFileName(stackTraceLine);System.out.println("fileName = " + fileName);String path = getPath(stackTraceLine).toString();System.out.println("path = " + path);String codeFile = getCodeFile(stackTraceLine);System.out.println("codeFile = " + codeFile);}}
简单地设计一个prompt,也就是我们需要发给GPT的信息。
private static final String prompt = "\"You are a good Java engineer, please analyze the following error and provide possible solutions." +"The error message is : %s, The error stack is : %s. The code file is : %s";
当测试方法抛出异常后,我们将异常相关信息以及代码文件发送给GPT让它进行分析。
package com.jd.jr.parrot.junit;import com.jd.jr.parrot.ExceptionUtil;import com.jd.jr.parrot.llm.LLMUtil;import java.util.Arrays;import lombok.extern.slf4j.Slf4j;import org.junit.rules.TestWatcher;import org.junit.runner.Description;/*** created on 2023/7/28 14:07** @author Dragonchu*/@Slf4jpublic class OverWatch extends TestWatcher {private static final String prompt = "\"You are a good Java engineer, please analyze the following error and provide possible solutions." +"The error message is : %s, The error stack is : %s. The code file is : %s";@Overrideprotected void failed(Throwable e, Description description) {super.failed(e, description);String codeFile = ExceptionUtil.getCodeFile(e.getStackTrace()[0].toString());log.info(LLMUtil.sendMsg(String.format(prompt, e.getMessage(), Arrays.toString(e.getStackTrace()),codeFile)));}}
让AI给我一份错误代码
本着能不自己干就不自己干的态度,我们让AI生成一份有错误的代码。
这是我与AI的对话:
我:
给我一份会抛出异常的代码,我希望这份代码稍微复杂一点,且依赖了一些外部包,它错误的原因比较难以排查。
AI:
以下是一个稍微复杂一点且依赖外部包的会抛出异常的Java代码示例:
importorg.jsoup.Jsoup;importorg.jsoup.nodes.Document;importorg.jsoup.nodes.Element;importorg.jsoup.select.Elements;importjava.io.IOException;publicclassExceptionExample{ publicstaticvoidmain(String[]args){ try{ // 使用Jsoup抓取网页内容 Documentdoc=Jsoup.connect("https://www.example.com").get(); // 解析网页内容 Elementtitle=doc.select("title").first(); System.out.println("Title: "+title.text()); // 尝试获取一个不存在的元素 ElementnonExistentElement=doc.select("#nonexistent-element").first(); // 在非空元素上调用方法,会抛出NullPointerException nonExistentElement.text(); }catch(IOExceptione){ e.printStackTrace(); } }}
这段代码使用外部包Jsoup来抓取网页内容并解析,然后尝试获取一个不存在的元素并在其上调用方法,会抛出NullPointerException异常。该异常可能比较难以排查,需要检查代码是否正确处理了异常情况,以及是否正确使用外部包的API等。
测试,让AI在junit测试失败后分析代码
简单写一个测试:
package com.jd.jr.parrot;import com.jd.jr.parrot.junit.OverWatch;import java.io.IOException;import org.junit.FixMethodOrder;import org.junit.Rule;import org.junit.Test;import org.junit.runners.MethodSorters;/*** created on 2023/7/28 14:14** @author Dragonchu*/@FixMethodOrder(MethodSorters.NAME_ASCENDING)public class OverWatchTest {@Rulepublic OverWatch overWatch = new OverWatch();@Testpublic void jsoupTest() throws IOException {ExceptionExample.main(new String[]{});}}
测试结果
