开发者社区 > 博文 > Junit4遇上chatGPT
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

Junit4遇上chatGPT

  • jd****
  • 2024-05-31
  • IP归属:北京
  • 100浏览

    这是一篇适合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
     */
    @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();
        }
    
        /**
         * 发送 post 请求
         *
         * @param url     请求地址
         * @param jsonStr Form表单json字符串
         * @return 请求结果
         */
        public static String postGPT(String url, String jsonStr) {
            // 创建httpClient
            CloseableHttpClient 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
     */
    @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给我一份错误代码

    本着能不自己干就不自己干的态度,我们让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 {
        @Rule
        public OverWatch overWatch = new OverWatch();
    
        @Test
        public void jsoupTest() throws IOException {
            ExceptionExample.main(new String[]{});
        }
    }
    
    

    测试结果




    文章数
    1
    阅读量
    0

    作者其他文章