Junit4遇上ChatGPT,會擦出什么火花!
這是一篇適合Java工程師體質(zhì)的AI開發(fā)教程。
本教程會教你寫一個簡單的junit4的Rule,該Rule在基于junit4的測試方法失敗后,自動向GPT發(fā)送錯誤信息并通過GPT分析得出代碼修改建議。
首先向AI問好
簡單的通過AI,讓它基于json格式生成相應(yīng)的類文件,這樣方便我們在代碼中使用,而不用顯示寫json字符串。
package com.jd.jr.parrot.llm;
import lombok.Data;
/**
* created on 2023/7/25 20:07
*
* @author Dragonchu
*/
@Data
public 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
*/
@Data
public 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
*/
@Data
public 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
*/
@Data
public 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
*/
@Data
public 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
*/
@Slf4j
public 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();
}
/**
* 發(fā)送 post 請求
*
* @param url 請求地址
* @param jsonStr Form表單json字符串
* @return 請求結(jié)果
*/
public static String postGPT(String url, String jsonStr) {
// 創(chuàng)建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
// 創(chuàng)建post請求方式實例
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-type", "application/json;charset=utf-8");
httpPost.setHeader("Authorization", apiKey);
// 設(shè)置參數(shù)---設(shè)置消息實體 也就是攜帶的數(shù)據(jù)
StringEntity entity = new StringEntity(jsonStr, Charset.forName("UTF-8"));
// 設(shè)置編碼格式
entity.setContentEncoding("UTF-8");
// 發(fā)送Json格式的數(shù)據(jù)請求
entity.setContentType("application/json");
// 把請求消息實體塞進去
httpPost.setEntity(entity);
// 執(zhí)行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,使得我們可以在測試代碼中嵌入相應(yīng)的邏輯,比如在執(zhí)行代碼前輸出方法名,捕獲異常等。TestWatcher是一個抽象類,通過覆蓋該類我們可以得到一個滿足我們需求的Rule。
我們希望能在測試方法失敗拋出異常后,直接將異常信息和異常堆棧發(fā)送給GPT,同時,也要將出錯的代碼文件發(fā)送給GPT(注意敏感信息哦,本文只是一個教程,請勿私自將敏感代碼發(fā)送給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()) {
// 讀取文件內(nèi)容
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);
}
}
簡單地設(shè)計一個prompt,也就是我們需要發(fā)給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";
當(dāng)測試方法拋出異常后,我們將異常相關(guān)信息以及代碼文件發(fā)送給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
*/
@Slf4j
public 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";
@Override
protected 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給我一份錯誤代碼:
本著能不自己干就不自己干的態(tài)度,我們讓AI生成一份有錯誤的代碼。
這是我與AI的對話:
我:
給我一份會拋出異常的代碼,我希望這份代碼稍微復(fù)雜一點,且依賴了一些外部包,它錯誤的原因比較難以排查。
AI:
以下是一個稍微復(fù)雜一點且依賴外部包的會拋出異常的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抓取網(wǎng)頁內(nèi)容 Documentdoc=Jsoup.connect("https://www.example.com").get(); // 解析網(wǎng)頁內(nèi)容 Elementtitle=doc.select("title").first(); System.out.println("Title: "+title.text()); // 嘗試獲取一個不存在的元素 Elementnnotallow=doc.select("#nonexistent-element").first(); // 在非空元素上調(diào)用方法,會拋出NullPointerException nonExistentElement.text(); }catch(IOExceptione){ e.printStackTrace(); } }}
這段代碼使用外部包Jsoup來抓取網(wǎng)頁內(nèi)容并解析,然后嘗試獲取一個不存在的元素并在其上調(diào)用方法,會拋出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 {
@Rule
public OverWatch overWatch = new OverWatch();
@Test
public void jsoupTest() throws IOException {
ExceptionExample.main(new String[]{});
}
}
測試結(jié)果:
圖片