实测 AI 写后端项目:从 CRUD 到测试命令,哪些真能省时间?
AI编程 测评报告|附完整命令
本文是一份面向开发者、技术负责人和团队管理者的 AI 编程工具测评报告。文章将从环境准备、典型任务、评测维度、实际表现、成本效率、适用场景和完整命令示例等方面,对 AI 编程助手在真实开发流程中的能力进行系统分析。文末附有可直接复现的完整命令,方便读者快速搭建测试环境并进行二次验证。
一、为什么要做 AI 编程测评?
过去几年,AI 编程工具从“代码补全插件”逐渐发展为“智能研发助手”。它们不仅能补全函数、生成单元测试,还能解释代码、定位 bug、重构项目结构,甚至根据自然语言需求生成完整应用原型。
但在实际团队落地过程中,很多开发者会遇到几个问题:
- AI 生成的代码能不能直接用?
- 复杂项目中,AI 是否理解上下文?
- AI 能否提升真实研发效率,而不是制造更多返工?
- 不同任务下,AI 编程工具的能力边界在哪里?
- 如何设计一套相对客观的测评流程?
因此,本次测评不只关注“AI 能不能写代码”,而是更关注它在真实开发链路中的综合表现,包括需求理解、代码质量、可维护性、测试能力、错误修复能力以及工程化适配能力。
二、测评目标
本次 AI 编程测评主要围绕以下几个目标展开:
| 测评目标 | 说明 |
|---|---|
| 代码生成能力 | 根据需求生成可运行代码 |
| 代码理解能力 | 阅读已有项目并解释逻辑 |
| Bug 修复能力 | 根据报错信息定位并修复问题 |
| 单元测试能力 | 为核心逻辑生成测试用例 |
| 重构能力 | 优化代码结构,提升可维护性 |
| 工程化能力 | 是否能适配真实项目目录、依赖、脚本 |
| 稳定性 | 多轮交互后是否保持上下文一致 |
| 成本收益 | 节省时间是否大于调试成本 |
三、测评环境
为了让测评更接近真实开发场景,本次选取了一个常见的后端服务作为测试项目:使用 Node.js + Express + SQLite 构建一个简单的任务管理 API。
项目功能包括:
- 用户创建任务;
- 查询任务列表;
- 更新任务状态;
- 删除任务;
- 参数校验;
- 基础错误处理;
- 单元测试覆盖核心接口。
1. 系统环境
本次测试环境如下:
操作系统:macOS / Linux / Windows WSL 均可
Node.js:v20+
npm:v10+
数据库:SQLite
测试框架:Jest + Supertest
Web 框架:Express
2. 项目目录设计
目标项目结构如下:
ai-coding-test/
├── package.json
├── src/
│ ├── app.js
│ ├── server.js
│ ├── db.js
│ ├── routes/
│ │ └── tasks.js
│ └── services/
│ └── taskService.js
└── tests/
└── tasks.test.js
这个项目规模不大,但足以覆盖 AI 编程工具常见的能力边界:它既包含 API 开发,也包含数据库操作、错误处理和测试用例编写。
四、测评方法
本次测评分为五轮任务,每一轮都模拟真实开发中的常见场景。
第一轮:从零生成项目
要求 AI 根据自然语言描述生成一个 Express + SQLite 的任务管理 API 项目,并提供启动方式。
观察重点:
- 是否能正确设计项目结构;
- 是否能生成完整依赖;
- 是否能初始化数据库;
- 是否能提供可运行命令;
- 是否存在明显语法错误。
第二轮:补充接口能力
要求 AI 添加以下接口:
POST /tasks
GET /tasks
PATCH /tasks/:id
DELETE /tasks/:id
每个任务包含:
{
"id": 1,
"title": "学习 AI 编程",
"completed": false,
"createdAt": "2026-01-01T00:00:00.000Z"
}
观察重点:
- 参数校验是否充分;
- HTTP 状态码是否合理;
- 数据库增删改查是否正确;
- 异常场景是否处理。
第三轮:生成单元测试
要求 AI 使用 Jest 和 Supertest 编写接口测试。
观察重点:
- 测试是否能真正运行;
- 测试用例是否覆盖正常和异常场景;
- 是否能隔离测试数据库;
- 是否存在强依赖执行顺序的问题。
第四轮:根据报错修复 Bug
故意制造一个数据库字段名错误或异步调用错误,然后把报错信息提供给 AI,观察它是否能定位并修复。
观察重点:
- 是否能根据堆栈信息定位文件;
- 是否能解释错误原因;
- 是否给出最小修复方案;
- 是否引入新的问题。
第五轮:代码重构
要求 AI 对项目进行分层重构,把路由逻辑和业务逻辑拆分。
观察重点:
- 是否保持接口兼容;
- 是否降低重复代码;
- 是否提升可读性;
- 是否破坏测试。
五、完整命令:创建可复现测试项目
下面是一套完整命令,可用于快速初始化测试项目。读者可以复制执行,然后使用任意 AI 编程工具进行辅助开发或对照测试。
1. 创建项目目录
mkdir ai-coding-test
cd ai-coding-test
npm init -y
2. 安装运行依赖
npm install express sqlite3
3. 安装开发依赖
npm install -D jest supertest nodemon
4. 修改 package.json
可以使用以下命令快速覆盖 package.json:
cat > package.json <<'EOF'
{
"name": "ai-coding-test",
"version": "1.0.0",
"description": "AI coding evaluation project",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"test": "jest --runInBand"
},
"keywords": [
"ai",
"coding",
"express",
"sqlite",
"test"
],
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.18.3",
"sqlite3": "^5.1.7"
},
"devDependencies": {
"jest": "^29.7.0",
"nodemon": "^3.1.0",
"supertest": "^6.3.4"
}
}
EOF
六、核心代码示例
1. 创建目录
mkdir -p src/routes src/services tests
2. 创建数据库文件:src/db.js
cat > src/db.js <<'EOF'
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:');
function initDb() {
return new Promise((resolve, reject) => {
db.run(
`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER NOT NULL DEFAULT 0,
createdAt TEXT NOT NULL
)
`,
err => {
if (err) reject(err);
else resolve();
}
);
});
}
function run(sql, params = []) {
return new Promise((resolve, reject) => {
db.run(sql, params, function (err) {
if (err) reject(err);
else resolve(this);
});
});
}
function get(sql, params = []) {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
}
function all(sql, params = []) {
return new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
}
module.exports = {
db,
initDb,
run,
get,
all
};
EOF
3. 创建业务层:src/services/taskService.js
cat > src/services/taskService.js <<'EOF'
const { run, get, all } = require('../db');
function normalizeTask(row) {
if (!row) return null;
return {
id: row.id,
title: row.title,
completed: Boolean(row.completed),
createdAt: row.createdAt
};
}
async function createTask(title) {
if (!title || typeof title !== 'string' || !title.trim()) {
const error = new Error('title is required');
error.statusCode = 400;
throw error;
}
const createdAt = new Date().toISOString();
const result = await run(
'INSERT INTO tasks (title, completed, createdAt) VALUES (?, ?, ?)',
[title.trim(), 0, createdAt]
);
const row = await get('SELECT * FROM tasks WHERE id = ?', [result.lastID]);
return normalizeTask(row);
}
async function listTasks() {
const rows = await all('SELECT * FROM tasks ORDER BY id DESC');
return rows.map(normalizeTask);
}
async function updateTask(id, payload) {
const task = await get('SELECT * FROM tasks WHERE id = ?', [id]);
if (!task) {
const error = new Error('task not found');
error.statusCode = 404;
throw error;
}
const nextTitle =
typeof payload.title === 'string' && payload.title.trim()
? payload.title.trim()
: task.title;
const nextCompleted =
typeof payload.completed === 'boolean'
? payload.completed
: Boolean(task.completed);
await run(
'UPDATE tasks SET title = ?, completed = ? WHERE id = ?',
[nextTitle, nextCompleted ? 1 : 0, id]
);
const updated = await get('SELECT * FROM tasks WHERE id = ?', [id]);
return normalizeTask(updated);
}
async function deleteTask(id) {
const task = await get('SELECT * FROM tasks WHERE id = ?', [id]);
if (!task) {
const error = new Error('task not found');
error.statusCode = 404;
throw error;
}
await run('DELETE FROM tasks WHERE id = ?', [id]);
return normalizeTask(task);
}
module.exports = {
createTask,
listTasks,
updateTask,
deleteTask
};
EOF
4. 创建路由:src/routes/tasks.js
cat > src/routes/tasks.js <<'EOF'
const express = require('express');
const {
createTask,
listTasks,
updateTask,
deleteTask
} = require('../services/taskService');
const router = express.Router();
router.post('/', async (req, res, next) => {
try {
const task = await createTask(req.body.title);
res.status(201).json(task);
} catch (err) {
next(err);
}
});
router.get('/', async (req, res, next) => {
try {
const tasks = await listTasks();
res.json(tasks);
} catch (err) {
next(err);
}
});
router.patch('/:id', async (req, res, next) => {
try {
const task = await updateTask(Number(req.params.id), req.body);
res.json(task);
} catch (err) {
next(err);
}
});
router.delete('/:id', async (req, res, next) => {
try {
const task = await deleteTask(Number(req.params.id));
res.json(task);
} catch (err) {
next(err);
}
});
module.exports = router;
EOF
5. 创建 app:src/app.js
cat > src/app.js <<'EOF'
const express = require('express');
const tasksRouter = require('./routes/tasks');
const app = express();
app.use(express.json());
app.get('/health', (req, res) => {
res.json({
status: 'ok'
});
});
app.use('/tasks', tasksRouter);
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
error: err.message || 'Internal Server Error'
});
});
module.exports = app;
EOF
6. 创建启动文件:src/server.js
cat > src/server.js <<'EOF'
const app = require('./app');
const { initDb } = require('./db');
const PORT = process.env.PORT || 3000;
async function start() {
await initDb();
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
}
start().catch(err => {
console.error(err);
process.exit(1);
});
EOF
七、测试代码与完整命令
1. 创建测试文件:tests/tasks.test.js
cat > tests/tasks.test.js <<'EOF'
const request = require('supertest');
const app = require('../src/app');
const { initDb } = require('../src/db');
beforeAll(async () => {
await initDb();
});
describe('Tasks API', () => {
test('GET /health should return ok', async () => {
const res = await request(app).get('/health');
expect(res.statusCode).toBe(200);
expect(res.body.status).toBe('ok');
});
test('POST /tasks should create a task', async () => {
const res = await request(app)
.post('/tasks')
.send({
title: '学习 AI 编程'
});
expect(res.statusCode).toBe(201);
expect(res.body.id).toBeDefined();
expect(res.body.title).toBe('学习 AI 编程');
expect(res.body.completed).toBe(false);
});
test('POST /tasks should validate title', async () => {
const res = await request(app)
.post('/tasks')
.send({
title: ''
});
expect(res.statusCode).toBe(400);
expect(res.body.error).toBe('title is required');
});
test('GET /tasks should return task list', async () => {
await request(app)
.post('/tasks')
.send({
title: '写测试用例'
});
const res = await request(app).get('/tasks');
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBeGreaterThan(0);
});
test('PATCH /tasks/:id should update task', async () => {
const created = await request(app)
.post('/tasks')
.send({
title: '待更新任务'
});
const res = await request(app)
.patch(`/tasks/${created.body.id}`)
.send({
completed: true
});
expect(res.statusCode).toBe(200);
expect(res.body.completed).toBe(true);
});
test('DELETE /tasks/:id should delete task', async () => {
const created = await request(app)
.post('/tasks')
.send({
title: '待删除任务'
});
const res = await request(app)
.delete(`/tasks/${created.body.id}`);
expect(res.statusCode).toBe(200);
expect(res.body.id).toBe(created.body.id);
});
test('PATCH /tasks/:id should return 404 when task not found', async () => {
const res = await request(app)
.patch('/tasks/999999')
.send({
completed: true
});
expect(res.statusCode).toBe(404);
expect(res.body.error).toBe('task not found');
});
});
EOF
2. 运行测试
npm test
3. 启动服务
npm run dev
4. 使用 curl 验证接口
健康检查
curl http://localhost:3000/health
创建任务
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title":"学习 AI 编程"}'
查询任务列表
curl http://localhost:3000/tasks
更新任务
curl -X PATCH http://localhost:3000/tasks/1 \
-H "Content-Type: application/json" \
-d '{"completed":true}'
删除任务
curl -X DELETE http://localhost:3000/tasks/1
八、AI 编程工具实际表现分析
1. 需求理解能力:表现较好
在简单、明确、边界清晰的需求下,AI 编程工具通常可以快速生成可运行代码。比如“使用 Express 创建 CRUD 接口”“使用 Jest 编写测试”这类任务,AI 的表现相当稳定。
但如果需求描述比较模糊,例如“做一个任务系统”“增加权限控制”“支持多租户”,AI 很容易生成看似完整但缺少关键约束的代码。例如:
- 没有考虑用户身份;
- 数据模型设计过于简单;
- 权限校验逻辑缺失;
- 没有考虑并发与事务;
- 错误处理不统一。
因此,AI 编程对“需求表达质量”非常敏感。开发者给出的提示越具体,AI 输出的质量通常越高。
2. 代码生成能力:适合原型和常规模块
AI 在生成模板化代码方面非常高效。比如创建路由、编写基本 CRUD、封装数据库访问、生成测试样例等任务,AI 可以明显节省时间。
本次测评中,AI 在以下任务中表现良好:
- 生成 Express 项目骨架;
- 编写 RESTful API;
- 封装 SQLite 查询;
- 生成基础错误处理中间件;
- 编写 Jest + Supertest 测试;
- 生成 curl 调试命令。
不过,AI 生成代码仍然存在一些典型问题:
-
依赖版本可能不匹配
有时 AI 会使用已经过时的包版本,或者生成与当前依赖不兼容的写法。 -
异步错误处理不完整
对于 Express 中的 async handler,如果没有正确 try/catch 或 next(err),可能导致异常未被统一处理。 -
测试隔离不足
AI 很容易生成依赖执行顺序的测试,例如先创建任务,再假设任务 ID 一定是 1。 -
边界场景考虑有限
比如空字符串、非法 ID、超长字段、重复提交等情况往往需要人工补充。
3. Bug 修复能力:依赖报错信息质量
AI 对常见报错的处理能力较强。例如:
SQLITE_ERROR: no such column: created_at
如果提供完整堆栈信息,AI 通常能判断出是数据库字段名与 SQL 查询字段不一致,并给出修改建议。
但如果只告诉 AI “接口报错了”或“测试不通过”,它的修复方案可能比较发散,甚至会重写大量无关代码。因此,在让 AI 修 Bug 时,建议提供以下信息:
- 报错完整堆栈;
- 触发问题的命令;
- 相关文件内容;
- 预期行为;
- 实际行为;
- 最近修改过的代码。
一个比较好的提示词示例:
下面是运行 npm test 后的报错信息。
请你只定位问题原因,并给出最小修改方案,不要重构整个项目。
报错信息:
SQLITE_ERROR: no such column: created_at
相关代码:
[粘贴 db.js 和 taskService.js]
4. 单元测试能力:能生成,但需要人工审查
AI 生成测试用例的速度很快,尤其适合覆盖基础接口场景。但在真实项目中,AI 生成的测试代码仍然需要人工审查。
常见问题包括:
- 测试之间共享状态;
- 测试数据未清理;
- 断言过于宽松;
- 只测 happy path;
- 没有覆盖异常输入;
- 对时间、ID、排序等结果做了不稳定假设。
本次测评中,AI 能较好生成基础接口测试,但对于测试数据库隔离、beforeEach 清理数据、mock 外部依赖等复杂测试实践,还需要开发者进一步引导。
5. 重构能力:适合局部重构,不适合盲目全局重构
AI 在“把这段重复逻辑抽成函数”“把路由层和服务层拆开”“优化错误处理”这类局部重构任务中表现不错。
但如果直接要求:
请帮我重构整个项目,让代码更优雅。
AI 可能会:
- 改动范围过大;
- 引入新的抽象;
- 改变接口行为;
- 修改测试期望;
- 生成并不存在的文件引用;
- 使项目从“可运行”变成“看起来更好但跑不起来”。
因此,AI 重构更适合使用“小步快跑”策略。例如:
请只重构 src/routes/tasks.js,把数据库操作移动到 service 层。
要求:
1. 不改变接口路径;
2. 不改变响应格式;
3. 不修改测试文件;
4. 给出每个新增或修改文件的完整内容。
这种约束明确的提示方式,会显著提升 AI 输出质量。
九、效率评估
根据本次测评,AI 编程工具在不同任务中的效率提升大致如下:
| 任务类型 | 人工耗时 | AI 辅助耗时 | 效率提升 |
|---|---|---|---|
| 项目骨架搭建 | 20-40 分钟 | 5-10 分钟 | 高 |
| CRUD 接口开发 | 40-90 分钟 | 15-30 分钟 | 高 |
| 基础测试编写 | 30-60 分钟 | 10-25 分钟 | 中高 |
| Bug 定位 | 10-60 分钟 | 5-30 分钟 | 中 |
| 代码重构 | 30-120 分钟 | 20-90 分钟 | 不稳定 |
| 架构设计 | 1-3 小时 | 仍需人工主导 | 有限 |
总体来看,AI 对“明确、重复、模板化”的任务提升最明显;对“复杂架构、业务抽象、长期演进”的任务帮助有限,仍需开发者主导判断。
十、推荐使用方式
1. 把 AI 当作初级开发助手,而不是最终决策者
AI 可以帮你快速产出第一版代码,但最终是否合并,需要经过:
- 代码审查;
- 自动化测试;
- 安全检查;
- 性能验证;
- 人工业务确认。
2. 提示词要具体
低质量提示词:
帮我写一个任务系统。
高质量提示词:
请使用 Node.js、Express 和 SQLite 实现一个任务管理 API。
要求:
1. 提供 POST /tasks、GET /tasks、PATCH /tasks/:id、DELETE /tasks/:id;
2. task 字段包括 id、title、completed、createdAt;
3. title 不能为空;
4. 使用 service 层封装业务逻辑;
5. 使用 Jest 和 Supertest 编写测试;
6. 输出完整文件内容和运行命令。
3. 每次只让 AI 做一件事
不要一次要求 AI 完成需求分析、架构设计、编码、测试、部署和优化。更好的方式是分阶段:
- 先生成目录结构;
- 再生成核心代码;
- 再补测试;
- 再根据测试报错修复;
- 最后进行小范围重构。
4. 一定要运行命令验证
AI 生成代码后,第一件事不是继续让它优化,而是运行:
npm install
npm test
npm run dev
如果测试失败,应把失败信息反馈给 AI,而不是直接要求“重新写一遍”。
十一、主要结论
通过本次测评,可以得出以下结论:
-
AI 编程工具已经具备较强的实用价值
对于日常开发中的样板代码、接口生成、测试补全、文档说明等任务,AI 能显著提升效率。 -
AI 适合做加速器,不适合做负责人
它可以快速生成方案,但缺乏对业务长期演进、系统边界和组织约束的深度理解。 -
提示词质量决定输出质量
明确技术栈、接口定义、输出格式、限制条件和验收标准,是提高 AI 编程质量的关键。 -
测试是 AI 编程落地的安全网
没有自动化测试的 AI 编程,很容易变成“看起来能跑”的幻觉式开发。 -
最佳实践是人机协作
开发者负责判断、设计、审查和验收;AI 负责生成、解释、补全和提供备选方案。
十二、最终评分
以下评分基于本次 Express + SQLite 项目的综合表现,满分 10 分:
| 维度 | 评分 | 说明 |
|---|---|---|
| 需求理解 | 8 | 明确需求下表现稳定 |
| 代码生成 | 8.5 | 常规项目生成效率高 |
| Bug 修复 | 7 | 依赖报错信息完整度 |
| 测试生成 | 7.5 | 基础测试较好,复杂隔离需人工补充 |
| 重构能力 | 7 | 局部重构可用,全局重构需谨慎 |
| 工程化能力 | 7.5 | 能生成命令和脚本,但仍需验证 |
| 综合评分 | 7.8 | 适合真实项目辅助开发 |
十三、总结
AI 编程不是简单地“让机器替你写代码”,而是一种新的研发协作方式。它降低了从想法到代码的启动成本,也提升了开发者处理重复任务的效率。但它并不会自动保证工程质量,更不会替代开发者对业务、架构和风险的判断。
如果团队想真正用好 AI 编程工具,建议建立一套基本规范:
- 所有 AI 生成代码必须经过 Review;
- 所有核心逻辑必须有测试;
- 禁止直接提交未经运行验证的代码;
- 对 AI 输出保持怀疑态度;
- 鼓励使用明确、可验证、分步骤的提示词;
- 将 AI 用于提效,而不是规避工程纪律。
一句话总结:AI 编程已经值得使用,但必须在工程规范和自动化测试的约束下使用。
只要方法正确,AI 不仅可以成为个人开发者的高效助手,也可以成为团队研发流程中的重要生产力工具。