AI热潮
从2023年3月OpenAI发布了GPT-4后,行业内掀起了一股AI浪潮,涌现了很多的AI工具,GitHub Copilot、通义灵码、Codeium等;工具功能也都大同小异,生成单测、代码解释、调优建议等。
最终效果
VScode插件+gpt单个/批量生成单测
VSCode插件
刚好学习怎么开发VSCode插件,不感兴趣可直接跳到AI生成单测模块。
插件项目
具体可查看文档:VSCode插件生成文档、vscode-extension-samples
1、安装必要的工具,Yeoman和VS Code Extension Generator:
sudo npm i -g yo generator-code
2、脚手架生成插件项目
yo code
3、项目分析
webpack.config.js文件中,配置了入口是src目录下的extension文件,打包后会生成dist/extension.js;
package.json中,main配置的就是./dist/extension.js,
4、调试
两种方式:
- 快捷键F5
- 点击 run and debug
会新打开一个窗口;按cmd+shift+p,输入Hello World,底部会有提示弹窗。支持代码添加断点,进行调试;
Q&A:找不到 Hello World 命令
在新窗口,按cmd+shift+p,输入Hello World,发现找不到命令
这个问题是因为 vscode 版本不一致造成的
package.json 文件中指定的 vscode 版本号高于本地版本,可修改版本或者更新vscode到最近版。
5、配置命令
默认带commands,可配置explorer/context,当文件右键时,会有快捷方式。
更改命令名,可修改title
// package.json
"contributes": {
"commands": [
{
"command": "ggt.helloWorld",
"title": "Hello World"
}
],
"menus": {
"explorer/context": [
{
"command": "ggt.helloWorld",
"group": "navigation"
}
]
}
},
// extension.ts
vscode.commands.registerCommand('ggt.helloWorld', () => {...})
6、发布
npm install vsce -g
// 登录,token: bvgzjoloaeo7a7qwlnptge54ydrinlhcqwg5qz3pvqt5e4t5paoq
vsce login wangsixiao
// 发布
vsce publish
// 可以在https://marketplace.visualstudio.com/manage/publishers/wangsixiao 查看发布的插件
AI生成单测
支持两种方式:
- new openai
- URL方式,使用axios请求
new openai
推荐一个能免费获取key的方式:GPT-API-free
点击即可获取到key,gpt-4有使用限制,3是完全免费的。
先上代码
openai使用文档:Chat Completions API
import OpenAI from "openai";
import * as vscode from "vscode";
// 由于我用的是上面的方法获取到的key,所以baseURL我配置的是他提供的转发host
const openai = new OpenAI({
baseURL: 'https://api.chatanywhere.com.cn',
});
/**
* 直接调用openai的方法
* @param path
* @param code
* @returns
*/
const UNIT_TEST_REQUEST = (path: string, code: string) =>
`Generate a unit test with the jest syntax, containing relevant assertions and required packages in a single 'describe' block. Import the functions from ${path} and use them to test the following code snippet:\n\n ${code}\n\n, 生成的内容只是单测代码,不要加其他的内容描述,确保测试用例能跑通,正确模拟react的hooks,正确模拟异步场景`;
export async function generateUnitTests(path: string, code: string) {
try {
const response = await openai.chat.completions.create({
// model: "gpt-3.5-turbo",
model: "gpt-4",
messages: [
{
role: "user",
content: UNIT_TEST_REQUEST(path, code),
},
],
temperature: 0,
});
return response.choices[0].message.content
}catch(error) {
vscode.window.showErrorMessage(
`Error generating unit tests from AI because of: ${error}`
);
}
}
1、baseURL
默认取的是process.env['OPENAI_BASE_URL'];
由于我用的是上面的方法获取到的key,所以baseURL我配置的是他提供的转发host;不配置,会有如下报错
2、Mac 配置 AI 环境变量 - process.env['OPENAI_API_KEY']
本文档只写了mac版本的配置步骤,windows可看详细的文档步骤:MacOS+NodeJS配置步骤
apiKey 支持传入,默认取环境里的OPENAI_API_KEY;
为了保证我们的key不被别人窃取,一般是配置在环境变量里。
配置步骤:
先判断shell是哪个应用。
echo $0 // 返回 -zsh 或 -bash
额外知识点:环境变量分为全局变量和用户变量;用户变量的配置文件由具体shell应用决定。
zsh版本
// 在Terminal执行命令, 打开配置文件
nano ~/.zshrc
// 配置OPENAI_API_KEY变量
export OPENAI_API_KEY='your-api-key'
// 执行下面的命令,保存&退出
Ctrl+O
Ctrl+X
// 执行source让变量生效
source ~/.zshrc
// 输入下面命令,看是否生效,如果返回你配置的值表示成功啦
echo $OPENAI_API_KEY // sk-xxxx
bash版本
// 在Terminal执行命令, 打开配置文件
nano ~/.bash_profile
// 配置OPENAI_API_KEY变量
export OPENAI_API_KEY='your-api-key sk-xxx'
// 执行下面的命令,保存&退出
Ctrl+O
Ctrl+X
// 执行source让变量生效
source ~/.bash_profile
// 输入下面命令,看是否生效,如果返回你配置的值表示成功啦
echo $OPENAI_API_KEY // sk-xxxx
axios请求
import axios from "axios";
/**
* URL方式,使用axios请求
* @param path
* @param code
* @returns
*/
export async function generateUnitTestsAxios(path: string, code: string) {
try {
const params = new URLSearchParams();
// 接口参数配置,主要看接口都需要什么参数,是啥形式的
params.append("currentUserId", "0");
params.append(
"request",
JSON.stringify({
model: "gpt-4",
messages: [
{
role: "user",
content: UNIT_TEST_REQUEST(path, code),
},
]
})
);
// 请求配置
const response = await axios({
url: "xxx",
timeout: 60000,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
},
responseType: "json",
data: params,
});
return response?.data?.data?.choices?.[0].message?.content;
} catch (error) {
vscode.window.showErrorMessage(
`Error generating unit tests from AI because of: ${error}`
);
throw new Error(`Error generating unit tests from AI because of: ${error}`);
}
}
批量生成
判断选中要生成单测的resource,如果是文件夹,则需要批量
const fileResource = await vscode.workspace.fs.stat(resource);
if (fileResource.type === vscode.FileType.Directory) {
// 批量生成
await traverseFolder(resource.fsPath);
} else {
// 单个生成
await singleFileGeneration(
vscode.window.activeTextEditor?.document.fileName
);
}
遍历文件夹,递归处理文件夹下所有符合需要生成单测的文件,调用单个文件生成单测方法
/**
* 遍历文件夹
* @param folderPath
* @returns
*/
export const traverseFolder = async (folderPath = "") => {
if (!folderPath) return;
const files = fs.readdirSync(folderPath);
for (let file of files) {
const filePath = path.join(folderPath, file);
const stats = fs.lstatSync(filePath);
if (stats.isDirectory()) {
traverseFolder(filePath); // 递归遍历子文件夹
} else {
if (supportFileTypes(file.split(".")?.[1])) {
await singleFileGeneration(filePath);
}
}
}
};
单个文件生成单测
/**
* 单个文件单测生成
* @param filePath
* @returns
*/
export const singleFileGeneration = async (filePath = "") => {
if (!filePath) return;
const fileParts: Array<string> = filePath ? filePath.split("/") : [];
const fileName: string = fileParts[fileParts.length - 1];
fileParts.pop();
const currentDirectory = fileParts.join("/");
if (filePath && supportFileTypes(fileName.split(".")?.[1])) {
// 获取文件内容
const code = await readFileContents(filePath);
if (code) {
// 生成单测
const content = await generateUnitTests(filePath, code);
// 写入文件
content && (await writeToFile(currentDirectory, fileName, content));
}
} else {
vscode.window.showErrorMessage(
"Not a valid file for unit test generation."
);
}
};