Copilot、Cursor、JetBrains 实战四场景:从测试到 CRUD 的关键写法
这三款 AI 编程助手——GitHub Copilot、Cursor 和 JetBrains AI Assistant——用熟了都能扛掉不少重复工作,但各自擅长的场景不太一样。下面四个场景是我在实际项目里最常碰到的,每个工具的操作要点和坑也一并说清楚。提示词怎么写、哪些事得自己兜底,直接影响生成质量。
一、生成测试用例:覆盖边界是关键
让 AI 写测试,最容易踩的坑就是边界覆盖不全。你得在提示里把极端情况列清楚,它才会认真处理。下面分工具说。
GitHub Copilot
写测试时,Copilot 靠注释来理解意图。比如 divide 函数,只写 # test divide,它大概率只生成正常除法。在注释里明确列出边界场景——除数为 0、极小浮点、被除数为 0——生成的质量就明显提升。
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# 测试 divide 函数,包括正常情况、除数为 0、除数为极小值、被除数为 0 等边界情况
def test_divide():
# 正常情况
assert divide(10, 2) == 5.0
# 边界情况 1:除数为 0,应抛出异常
try:
divide(10, 0)
assert False, "Expected ValueError"
except ValueError:
pass
# 边界情况 2:除数为极小值(接近 0 的浮点数)
assert abs(divide(1, 1e-10) - 1e10) < 1e-5
# 边界情况 3:被除数为 0
assert divide(0, 5) == 0.0
如果生成还不全,就继续补注释,比如再加 # 测试负数除法,Copilot 会接着补上。
JetBrains AI Assistant
在 PyCharm 或 IntelliJ 里,右键点函数选 AI Assistant → Generate Tests,对话框里一定要把要求写死。默认生成往往只覆盖最常规的路径,不加这步很容易漏掉异常和极值。
提示词示例:
生成 JUnit 5 测试类,覆盖以下场景:
- 正常整数除法(10/2)
- 除数为 0(期望抛出 ArithmeticException)
- 除数为极小浮点数(1e-10)
- 被除数为最大值(Double.MAX_VALUE)
- 除数为负数(-2)
它会生成类似下面的代码:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void testNormalDivision() {
assertEquals(5.0, Calculator.divide(10, 2));
}
@Test
void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> Calculator.divide(10, 0));
}
@Test
void testDivideByVerySmallNumber() {
assertEquals(1e10, Calculator.divide(1, 1e-10), 1e-5);
}
}
业务特有的边界(比如年龄不能为负)它猜不到,生成后得自己补上。
Cursor
Cursor 的聊天面板很适合自然语言交互。直接说明方法名、框架和边界要求,它会输出完整测试套件。例如:
为
UserService.createUser方法生成 Jest 测试,用例要求:
- 正常创建(姓名、邮箱有效)
- 邮箱格式错误(应抛出 ValidationError)
- 姓名超过 50 字符(边界测试)
- 空对象传入(边界测试)
生成结果可以直接运行,但需要注意:Cursor 有时会使用过时的断言风格,如果与项目配置不符,手动改一下就好。
// userService.test.js
import { createUser } from './userService';
test('正常创建用户', () => {
const user = createUser({ name: '张三', email: '[email protected]' });
expect(user.id).toBeDefined();
});
test('邮箱格式错误', () => {
expect(() => createUser({ name: '李四', email: 'invalid' }))
.toThrow('ValidationError');
});
test('姓名超过 50 字符', () => {
const longName = 'a'.repeat(51);
expect(() => createUser({ name: longName, email: '[email protected]' }))
.toThrow('Name too long');
});
二、解释遗留代码:别只问'这是啥'
遗留代码通常没注释、逻辑扭成一团。简单让 AI'解释这段代码',得到的往往是表面翻译,价值不大。得引导它做具体分析。
GitHub Copilot
结构化注释能大幅提升解释深度。在代码上方加一段类似这样的说明,Copilot 就会按点回答:
# 请逐行解释该函数的逻辑,重点说明:
# 1. 输入参数的作用和约束
# 2. 关键算法(如状态机、递归)
# 3. 可能的边界条件和 Bug 风险
# 4. 与其他模块的交互(如全局变量、外部调用)
def legacy_calculator(x, y, op):
if op == 'add':
return x + y
elif op == 'sub':
if x < y:
# 遗留代码中常见的反直觉逻辑
return 0 - (x - y)
return x - y
else:
return None
它会详细指出 0 - (x - y) 实际就是 y - x,很可能是历史补丁的残留;还会提醒浮点比较的风险。这种提示远比自己盯着代码猜高效。
JetBrains AI Assistant
选中代码块,右键选择 Explain Code,默认给出的是基础解释。想挖深一点,得在面板里追加提问,比如:
请分析该函数的时间复杂度,并指出潜在性能瓶颈。同时说明如果要重构为异步逻辑,哪些部分需要修改。
AI 会输出类似下面的分析:
时间复杂度:O(n²),因为有两层嵌套循环。 瓶颈点:内层循环可改用 Map 查找降至 O(n)。 重构注意:第 20 行的
data.get(i)可能返回 null,异步化前需统一处理。
这种交互式追问比一次性生成有用得多。
Cursor
Cursor 支持多轮对话,特别适合拆解复杂逻辑。我习惯这样问:
- 第一轮:用一句话总结该函数的目的。
- 第二轮:用 Mermaid 画出流程图。
- 第三轮:
flag变量什么时候被重置?生命周期是怎样的?
解释结果还能直接导出为 Markdown 文档,团队分享很方便。
三、代码重构与优化:先有测试,再信任 AI
AI 重构常犯两个毛病:过度优化(删掉重要日志或注释),或悄悄改变行为。我的原则是:没有测试就不重构,生成后立刻跑测试看行为是否一致。
GitHub Copilot
在代码上方写目标明确的注释,Copilot 会按指令改。比如:
// 重构该函数:
// 1. 将 SQL 查询提取为独立方法
// 2. 使用 Stream API 替代 for-loop
// 3. 添加空数据检查,避免 NPE
// 4. 保持原有接口不变
public List<User> getActiveUsers(List<User> allUsers) {
List<User> active = new ArrayList<>();
for (User u : allUsers) {
if (u.getStatus().equals("active")) {
active.add(u);
}
}
String sql = "SELECT * FROM users WHERE status = 'active'";
// ... 执行 SQL(实际上这段逻辑冗余,仅为示例)
return active;
}
生成的代码会提取 SQL 方法、用 Stream 筛选,并加上 null 检查:
public List<User> getActiveUsers(List<User> allUsers) {
if (allUsers == null) return Collections.emptyList();
String activeSql = getActiveUsersSql();
return allUsers.stream()
.filter(u -> "active".equals(u.getStatus()))
.collect(Collectors.toList());
}
private String getActiveUsersSql() {
return "SELECT * FROM users WHERE status = 'active'";
}
注意:Stream 在大数据量下不一定比 for-loop 快,如果性能敏感,最好做个对比。
JetBrains AI Assistant
选中代码,右键 → Refactor This Code,在提示框里把重构目标写具体:
目标:
- 将循环替换为 Java Stream
- 提取
validateInput方法,处理 null 和空集合- 移除未使用的变量
legacyFlag- 保持原有异常抛出逻辑
AI 会生成 diff 预览,确认后再点 Apply 直接修改。但一定要先备份或提交到 Git,它的撤销机制有时不够利索。
Cursor
光标建议在临时分支上操作。在 AI Chat 输入:
请重构
processData函数:
- 将 for 循环改为
Promise.all(异步化)- 使用 TypeScript 类型注解
- 限制并发数为 5(避免 DDoS)
- 输出重构前后对比
它会给出带类型的重构版本,并且标注限制并发的实现(比如嵌入 p-limit):
// 重构前
async function processData(items) {
const results = [];
for (const item of items) {
results.push(await fetchData(item));
}
return results;
}
// 重构后(限并发 + 类型)
async function processData<T>(items: T[]): Promise<ReturnType<typeof fetchData>[]> {
const limit = pLimit(5);
const limitedFetch = items.map(item => () => fetchData(item));
return await Promise.all(limitedFetch.map(fn => limit(fn)));
}
之后跑一下性能测试,避免并发引入的问题。
四、用自然语言生成 CRUD 脚手架
生成 CRUD 时,AI 最怕的是把框架搞错。提示里必须声明技术栈和具体约束,否则它可能给你 Django 版本却要求 Spring Boot。我的习惯是:先生成最基础的增删改查,通过测试后再一层层加业务校验和权限。
GitHub Copilot
新建文件,用注释描述需求,Copilot 能实时补全。比如 FastAPI + SQLAlchemy 的例子:
# 使用 FastAPI + SQLAlchemy 生成 User CRUD:
# - 数据库表:users(id, username, email, created_at)
# - 包含:创建、查询单个、查询所有、更新、删除
# - 验证:username 长度 3-20,email 必须包含 @
# - 返回 JSON,包含状态码和消息
Copilot 会生成完整的模型、Pydantic 校验和路由。拿到代码后,再手动补上用户名唯一性检查这样的业务逻辑:
@app.post("/users/", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
existing = db.query(UserModel).filter(UserModel.username == user.username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已存在")
db_user = UserModel(username=user.username, email=user.email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
JetBrains AI Assistant
在 Spring Boot 项目中,右键选 Generate Code from Description,写明要求:
使用 Spring Boot + JPA 生成 Product 资源的 CRUD REST API。 数据库表:products(id, name, price, stock, created_at) 要求:使用 Lombok 简化代码,添加全局异常处理,Swagger 文档支持,不生成测试代码。
它会自动生成实体类、Repository、Controller 和异常处理器骨架。之后再往 updateProduct 里加库存不能为负的校验:
if (dto.getStock() != null && dto.getStock() < 0) {
throw new IllegalArgumentException("库存不能为负数");
}
Cursor
可以把需求写成 prompt.txt 放在项目根目录,然后让 AI 生成代码。例如 Node.js + Express + Mongoose 的 Category 模块:
项目:Node.js + Express + Mongoose
实体:Category(分类)
字段:_id, name, description, is_active, created_at
需求:
- 生成完整 CRUD 路由(/api/categories)
- 使用 async/await
- 添加分页查询(页码、每页数量)
- 返回格式:{ code: 0, data: {...}, msg: '成功' }
- 不包含认证
Cursor 会按 MVC 结构生成文件。拿到基础代码后,再在 updateCategory 里插入权限判断(假设已有认证中间件):
exports.updateCategory = async (req, res) => {
if (!req.user || req.user.role !== 'admin') {
return res.status(403).json({ code: -1, msg: '无权限操作' });
}
// ... 原有更新逻辑
};
几点实操心得
-
明确技术栈是第一原则
每次生成前,把框架、语言、版本写清楚。AI 默认常用版本可能偏旧,不写就会给你惊喜。 -
迭代,别指望一次完美
先让 AI 出基础版 → 测试 → 补提示 → 重新生成。三四个来回很正常。 -
测试自己补
AI 生成的代码即使通过了简单用例,边界 bug 也常有。每个 CRUD 操作都得有自己的边界测试,参考第一节的方法。 -
审查不能省
像审查同事代码一样查 AI 输出:SQL 注入风险、权限缺失、异常处理是否合理。 -
Prompt 存下来
把有效的提示词记录到项目PROMPTS.md,团队后来者直接复用,效率高很多。
这三个工具,Copilot 擅长实时补全和逐步引导修改,Cursor 的交互式提问在拆解遗留代码或复杂逻辑时很舒服,JetBrains 则强在 IDE 深度集成,一键生成测试、重构和脚手架最省事。组合使用效果更好,比如用 JetBrains 生成基础代码,切到 Copilot 优化注释和片段,最后在 Cursor 里生成测试和文档。
说到底,AI 处理的是重复性、低价值的搭建工作,业务逻辑、架构决策和最终的质量兜底,还是得留给自己。


