我用一个 FastAPI 小项目实测 AI 编程:能省事,也会埋坑(附完整命令)
AI编程 测评报告|附完整命令
一、前言:为什么要做一次“AI编程”测评?
过去一年,AI 编程工具从“代码补全助手”迅速进化成了“半自动开发协作伙伴”。从最早的函数级补全,到现在能够理解项目结构、生成单元测试、解释报错、重构模块、编写脚本甚至协助完成完整需求,AI 编程已经不再是一个新鲜概念,而是正在改变开发流程的现实工具。
但是,AI 编程工具到底能不能真正提升效率?它适合什么场景?在哪些任务上表现稳定,在哪些地方容易出错?如果把它放进真实项目中,它是“生产力工具”,还是“制造隐患的黑盒”?这些问题不能只靠宣传文案判断,必须通过实际测评来验证。
本文将围绕一次较完整的 AI 编程测评展开,内容包括:
- 测评目标与方法;
- 测试环境搭建;
- 常见开发任务设计;
- AI 编程在需求理解、代码生成、调试、重构、测试等环节的表现;
- 实测过程中使用的完整命令;
- 最终结论与使用建议。
本文不是单纯吹捧 AI,也不是刻意否定 AI,而是从实际开发角度,尽可能客观地观察 AI 编程工具在当前阶段的能力边界。
二、测评目标
本次测评主要关注以下几个问题:
1. AI 是否能准确理解需求?
很多 AI 编程工具看似可以快速生成代码,但实际项目中最重要的不是“能写代码”,而是“能不能写对代码”。如果需求理解有偏差,即使代码语法正确,也可能产生业务逻辑错误。
2. AI 生成代码的可运行性如何?
代码能否直接运行,是最基础的衡量指标。本次测评会重点观察:
- 是否存在语法错误;
- 是否缺少依赖;
- 是否引入不存在的方法;
- 是否能够通过测试;
- 是否符合常见工程规范。
3. AI 是否能辅助调试?
真实开发中,代码出错是常态。一个优秀的 AI 编程助手不应该只会“从零生成”,还应该能够阅读错误日志、定位问题、给出修改方案。
4. AI 是否适合重构和维护?
很多项目的难点不在于新建一个 Demo,而在于维护已有代码。AI 是否能理解已有项目结构、减少重复代码、优化命名、拆分模块,是衡量其实用价值的重要标准。
5. AI 是否能生成可靠测试?
测试代码是 AI 编程的一个高价值场景。因为测试往往逻辑清晰、边界明确,如果 AI 能快速生成有效测试,将大幅提升开发效率。
三、测评环境
本次测评选择一个轻量但接近真实业务的小型后端项目作为样本,技术栈如下:
| 项目 | 说明 |
|---|---|
| 操作系统 | Ubuntu 22.04 / macOS 均可 |
| 编程语言 | Python 3.11 |
| Web 框架 | FastAPI |
| 数据库 | SQLite |
| ORM | SQLAlchemy |
| 测试框架 | pytest |
| 包管理工具 | pip / venv |
| 代码规范 | ruff |
| 类型检查 | mypy |
测评项目是一个简单的任务管理系统,包含以下功能:
- 用户创建任务;
- 查询任务列表;
- 更新任务状态;
- 删除任务;
- 按状态过滤任务;
- 自动生成基础单元测试;
- 修复已知 Bug;
- 对服务层代码进行重构。
选择这个项目的原因是:它足够简单,便于观察 AI 的基础能力;同时又包含数据库、API、测试、重构等常见开发环节,能够覆盖实际工作中的多个场景。
四、项目初始化命令
下面是完整的项目初始化命令。读者可以直接复制执行,搭建与本文类似的测评环境。
mkdir ai-coding-evaluation
cd ai-coding-evaluation
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install fastapi uvicorn sqlalchemy pydantic pytest httpx ruff mypy
创建项目目录:
mkdir app
mkdir tests
touch app/__init__.py
touch app/main.py
touch app/database.py
touch app/models.py
touch app/schemas.py
touch app/crud.py
touch tests/test_tasks.py
查看项目结构:
tree
如果系统未安装 tree,可以使用:
find . -maxdepth 3 -type f
五、基础代码准备
在正式让 AI 参与开发之前,先准备一个最小可运行项目。
1. 数据库配置
app/database.py:
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
DATABASE_URL = "sqlite:///./tasks.db"
engine = create_engine(
DATABASE_URL,
connect_args={"check_same_thread": False},
)
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine,
)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
2. 数据模型
app/models.py:
from sqlalchemy import Boolean, Column, Integer, String
from app.database import Base
class Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False, index=True)
description = Column(String, nullable=True)
completed = Column(Boolean, default=False)
3. Pydantic Schema
app/schemas.py:
from pydantic import BaseModel
class TaskBase(BaseModel):
title: str
description: str | None = None
class TaskCreate(TaskBase):
pass
class TaskUpdate(BaseModel):
title: str | None = None
description: str | None = None
completed: bool | None = None
class TaskOut(TaskBase):
id: int
completed: bool
class Config:
from_attributes = True
4. CRUD 逻辑
app/crud.py:
from sqlalchemy.orm import Session
from app import models, schemas
def create_task(db: Session, task: schemas.TaskCreate):
db_task = models.Task(
title=task.title,
description=task.description,
)
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
def list_tasks(db: Session, completed: bool | None = None):
query = db.query(models.Task)
if completed is not None:
query = query.filter(models.Task.completed == completed)
return query.all()
def get_task(db: Session, task_id: int):
return db.query(models.Task).filter(models.Task.id == task_id).first()
def update_task(db: Session, task_id: int, task: schemas.TaskUpdate):
db_task = get_task(db, task_id)
if db_task is None:
return None
update_data = task.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(db_task, key, value)
db.commit()
db.refresh(db_task)
return db_task
def delete_task(db: Session, task_id: int):
db_task = get_task(db, task_id)
if db_task is None:
return False
db.delete(db_task)
db.commit()
return True
5. FastAPI 入口
app/main.py:
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from app import crud, models, schemas
from app.database import Base, engine, get_db
Base.metadata.create_all(bind=engine)
app = FastAPI(title="AI Coding Evaluation API")
@app.post("/tasks", response_model=schemas.TaskOut)
def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):
return crud.create_task(db, task)
@app.get("/tasks", response_model=list[schemas.TaskOut])
def list_tasks(
completed: bool | None = None,
db: Session = Depends(get_db),
):
return crud.list_tasks(db, completed)
@app.get("/tasks/{task_id}", response_model=schemas.TaskOut)
def get_task(task_id: int, db: Session = Depends(get_db)):
db_task = crud.get_task(db, task_id)
if db_task is None:
raise HTTPException(status_code=404, detail="Task not found")
return db_task
@app.patch("/tasks/{task_id}", response_model=schemas.TaskOut)
def update_task(
task_id: int,
task: schemas.TaskUpdate,
db: Session = Depends(get_db),
):
db_task = crud.update_task(db, task_id, task)
if db_task is None:
raise HTTPException(status_code=404, detail="Task not found")
return db_task
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int, db: Session = Depends(get_db)):
ok = crud.delete_task(db, task_id)
if not ok:
raise HTTPException(status_code=404, detail="Task not found")
return {"deleted": True}
六、运行项目命令
启动服务:
uvicorn app.main:app --reload
浏览器打开:
http://127.0.0.1:8000/docs
使用 curl 创建任务:
curl -X POST "http://127.0.0.1:8000/tasks" \
-H "Content-Type: application/json" \
-d '{"title":"测试 AI 编程","description":"验证 AI 生成代码是否可用"}'
查询任务:
curl "http://127.0.0.1:8000/tasks"
更新任务:
curl -X PATCH "http://127.0.0.1:8000/tasks/1" \
-H "Content-Type: application/json" \
-d '{"completed":true}'
按状态过滤:
curl "http://127.0.0.1:8000/tasks?completed=true"
删除任务:
curl -X DELETE "http://127.0.0.1:8000/tasks/1"
七、测评任务设计
为了尽可能模拟真实开发场景,本次测评设计了六类任务。
任务一:根据需求生成接口
提示词示例:
请基于当前 FastAPI 项目,为任务管理系统增加“按 completed 状态过滤任务”的功能。
要求:
1. GET /tasks 支持 completed 查询参数;
2. completed 可选;
3. 不传时返回全部任务;
4. 传 true 时返回已完成任务;
5. 传 false 时返回未完成任务;
6. 给出需要修改的文件和完整代码。
任务二:生成单元测试
提示词示例:
请为当前 FastAPI 任务管理系统生成 pytest 测试。
要求覆盖:
1. 创建任务;
2. 查询任务列表;
3. 查询单个任务;
4. 更新任务 completed 状态;
5. 删除任务;
6. 查询不存在任务返回 404。
请给出 tests/test_tasks.py 的完整代码。
任务三:解释错误日志
提示词示例:
运行 pytest 后出现以下错误,请分析原因并给出修复方案:
sqlalchemy.exc.OperationalError: no such table: tasks
任务四:重构 CRUD 层
提示词示例:
请重构 app/crud.py,使代码更清晰,并增加类型标注。
要求:
1. 不改变对外接口;
2. 保持现有测试通过;
3. 避免重复查询;
4. 返回值类型尽量明确。
任务五:生成代码规范检查命令
提示词示例:
请给出本项目的 Python 代码格式检查、静态检查和类型检查命令。
工具包括 ruff 和 mypy。
任务六:发现潜在工程问题
提示词示例:
请审查当前项目,指出可能存在的工程问题,包括数据库初始化、测试隔离、异常处理、依赖管理、配置管理等方面,并给出改进建议。
八、AI 编程表现测评
1. 需求理解能力:表现较好,但依赖提示词质量
在接口功能扩展类任务中,AI 的表现整体不错。对于“按 completed 状态过滤任务”这类需求,AI 基本能够理解可选查询参数的含义,也知道应该分别修改 API 层和 CRUD 层。
较好的输出通常会包括:
main.py中增加completed: bool | None = None;crud.py中增加过滤逻辑;- 不传参数时返回全部;
- 传入参数时使用 SQLAlchemy
filter。
但如果提示词较模糊,例如只说“增加过滤功能”,AI 可能会默认使用字符串参数,如 status=done,而不是基于现有字段 completed。这说明 AI 并不是天然理解你的业务模型,它更像一个高水平但需要明确上下文的开发同事。
结论: AI 的需求理解能力足以应对中低复杂度功能开发,但前提是需求描述清晰,并提供必要上下文。
2. 代码生成能力:速度快,但需要人工 Review
在生成 FastAPI 接口、SQLAlchemy 查询、Pydantic Schema 等常见代码时,AI 的效率非常高。它可以在几十秒内生成一组看起来完整的代码,并且语法通常正确。
不过,实测中也发现了一些常见问题:
问题一:依赖版本不匹配
例如 Pydantic v1 和 v2 的配置写法不同。AI 有时会生成:
class Config:
orm_mode = True
但在 Pydantic v2 中,更推荐:
class Config:
from_attributes = True
这类问题不一定会立刻导致项目无法运行,但会出现警告或兼容性隐患。
问题二:测试数据库污染
AI 初次生成测试时,可能直接使用正式的 tasks.db,导致测试数据污染开发数据。比较合理的做法是测试时使用独立数据库,例如 sqlite:///./test.db,并在每次测试前后清理表。
问题三:异常处理不完整
对于不存在的任务,AI 通常能处理 404;但对于非法输入、空标题、超长字段等边界条件,除非明确要求,否则 AI 不一定主动补充。
结论: AI 生成代码的可用性较高,但不能跳过人工审查。它适合作为初稿生成器,而不适合完全无人值守地合并到主分支。
九、测试代码生成结果
一个较合理的 tests/test_tasks.py 示例:
import os
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.database import Base, get_db
from app.main import app
TEST_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
)
TestingSessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine,
)
def override_get_db():
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
@pytest.fixture(autouse=True)
def setup_database():
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
yield
Base.metadata.drop_all(bind=engine)
if os.path.exists("./test.db"):
os.remove("./test.db")
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)
def test_create_task():
response = client.post(
"/tasks",
json={
"title": "Write evaluation report",
"description": "Test AI coding capability",
},
)
assert response.status_code == 200
data = response.json()
assert data["title"] == "Write evaluation report"
assert data["completed"] is False
assert "id" in data
def test_list_tasks():
client.post("/tasks", json={"title": "Task 1"})
client.post("/tasks", json={"title": "Task 2"})
response = client.get("/tasks")
assert response.status_code == 200
data = response.json()
assert len(data) == 2
def test_get_task():
create_response = client.post("/tasks", json={"title": "Task detail"})
task_id = create_response.json()["id"]
response = client.get(f"/tasks/{task_id}")
assert response.status_code == 200
assert response.json()["title"] == "Task detail"
def test_update_task_completed():
create_response = client.post("/tasks", json={"title": "Need complete"})
task_id = create_response.json()["id"]
response = client.patch(
f"/tasks/{task_id}",
json={"completed": True},
)
assert response.status_code == 200
assert response.json()["completed"] is True
def test_filter_completed_tasks():
client.post("/tasks", json={"title": "Done"})
create_response = client.post("/tasks", json={"title": "Todo"})
task_id = create_response.json()["id"]
client.patch(f"/tasks/{task_id}", json={"completed": True})
response = client.get("/tasks?completed=true")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["completed"] is True
def test_delete_task():
create_response = client.post("/tasks", json={"title": "Delete me"})
task_id = create_response.json()["id"]
delete_response = client.delete(f"/tasks/{task_id}")
get_response = client.get(f"/tasks/{task_id}")
assert delete_response.status_code == 200
assert delete_response.json() == {"deleted": True}
assert get_response.status_code == 404
def test_get_not_found():
response = client.get("/tasks/999")
assert response.status_code == 404
运行测试:
pytest -q
如果想查看更详细输出:
pytest -vv
运行指定测试文件:
pytest tests/test_tasks.py -q
运行指定测试函数:
pytest tests/test_tasks.py::test_create_task -q
十、代码质量检查命令
使用 ruff 检查代码:
ruff check .
自动修复部分问题:
ruff check . --fix
格式化代码:
ruff format .
使用 mypy 做类型检查:
mypy app
如果希望同时运行测试和检查,可以执行:
ruff check .
ruff format --check .
mypy app
pytest -q
也可以写成一行:
ruff check . && ruff format --check . && mypy app && pytest -q
十一、AI 在调试任务中的表现
在调试方面,AI 的价值非常明显。例如遇到以下错误:
sqlalchemy.exc.OperationalError: no such table: tasks
AI 通常能够判断出原因可能是:
- 数据库表没有创建;
- 测试环境使用了新的 SQLite 数据库,但没有执行
Base.metadata.create_all(); - 应用启动时初始化的是正式数据库,而测试使用的是另一个数据库;
- 测试依赖覆盖不完整。
比较可靠的修复方案是,在测试 fixture 中显式执行:
Base.metadata.create_all(bind=engine)
并在测试结束后清理:
Base.metadata.drop_all(bind=engine)
这说明 AI 对常见框架错误具备较强的问题定位能力。尤其是当错误日志完整、项目上下文清晰时,它给出的方案往往能快速缩小排查范围。
但 AI 调试也有风险。它可能会给出多个可能原因,却没有区分优先级;有时也会建议“重新安装依赖”这类泛化方案,而不是针对问题本身。因此,开发者仍然需要具备基本判断力。
结论: AI 非常适合作为调试辅助工具,尤其适合解释报错、定位常见配置问题,但最终修复方案仍需实际运行验证。
十二、AI 在重构任务中的表现
让 AI 重构 crud.py 时,它通常能够完成以下改进:
- 增加类型标注;
- 拆分重复逻辑;
- 改善变量命名;
- 统一返回值;
- 减少部分重复查询。
例如改进后的代码可能如下:
from sqlalchemy.orm import Session
from app import models, schemas
def create_task(db: Session, task: schemas.TaskCreate) -> models.Task:
db_task = models.Task(
title=task.title,
description=task.description,
)
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
def list_tasks(
db: Session,
completed: bool | None = None,
) -> list[models.Task]:
query = db.query(models.Task)
if completed is not None:
query = query.filter(models.Task.completed == completed)
return list(query.all())
def get_task(db: Session, task_id: int) -> models.Task | None:
return db.query(models.Task).filter(models.Task.id == task_id).first()
def update_task(
db: Session,
task_id: int,
task: schemas.TaskUpdate,
) -> models.Task | None:
db_task = get_task(db, task_id)
if db_task is None:
return None
update_data = task.model_dump(exclude_unset=True)
for field_name, field_value in update_data.items():
setattr(db_task, field_name, field_value)
db.commit()
db.refresh(db_task)
return db_task
def delete_task(db: Session, task_id: int) -> bool:
db_task = get_task(db, task_id)
if db_task is None:
return False
db.delete(db_task)
db.commit()
return True
这类重构基本安全,因为没有改变函数签名和外部行为。但如果让 AI 进行更大规模的架构重构,比如引入 Repository 层、Service 层、配置系统、迁移工具等,它有时会一次性改动太多文件,导致难以 Review。
建议: 使用 AI 重构时,应遵循“小步提交”的原则。每次只让 AI 改一个明确目标,例如“只增加类型标注”“只消除重复代码”“只拆分数据库配置”,不要一次性提出过大的重构需求。
十三、完整命令汇总
为了方便复现,下面整理本次测评中使用的完整命令。
1. 创建项目
mkdir ai-coding-evaluation
cd ai-coding-evaluation
2. 创建虚拟环境
python3 -m venv .venv
source .venv/bin/activate
Windows PowerShell 可使用:
python -m venv .venv
.venv\Scripts\Activate.ps1
3. 安装依赖
python -m pip install --upgrade pip
pip install fastapi uvicorn sqlalchemy pydantic pytest httpx ruff mypy
4. 创建目录和文件
mkdir app tests
touch app/__init__.py
touch app/main.py
touch app/database.py
touch app/models.py
touch app/schemas.py
touch app/crud.py
touch tests/test_tasks.py
5. 启动服务
uvicorn app.main:app --reload
6. 创建任务
curl -X POST "http://127.0.0.1:8000/tasks" \
-H "Content-Type: application/json" \
-d '{"title":"测试 AI 编程","description":"验证 AI 生成代码是否可用"}'
7. 查询任务
curl "http://127.0.0.1:8000/tasks"
8. 查询单个任务
curl "http://127.0.0.1:8000/tasks/1"
9. 更新任务
curl -X PATCH "http://127.0.0.1:8000/tasks/1" \
-H "Content-Type: application/json" \
-d '{"completed":true}'
10. 按状态过滤
curl "http://127.0.0.1:8000/tasks?completed=true"
curl "http://127.0.0.1:8000/tasks?completed=false"
11. 删除任务
curl -X DELETE "http://127.0.0.1:8000/tasks/1"
12. 运行测试
pytest -q
pytest -vv
13. 代码检查
ruff check .
14. 自动修复
ruff check . --fix
15. 代码格式化
ruff format .
16. 格式检查
ruff format --check .
17. 类型检查
mypy app
18. 一键执行全部检查
ruff check . && ruff format --check . && mypy app && pytest -q
十四、综合评分
基于本次测评结果,可以给出如下评分:
| 测评维度 | 评分 | 说明 |
|---|---|---|
| 需求理解 | 8/10 | 清晰需求下表现很好,模糊需求容易自行假设 |
| 代码生成 | 8/10 | 常见框架代码生成速度快,可用性较高 |
| 调试能力 | 8.5/10 | 对常见报错解释准确,能快速给出方向 |
| 测试生成 | 7.5/10 | 能覆盖主流程,但边界测试需人工补充 |
| 重构能力 | 7/10 | 小范围重构可靠,大范围重构需谨慎 |
| 工程化意识 | 6.5/10 | 对配置、迁移、环境隔离等问题不总是主动处理 |
| 安全可靠性 | 6/10 | 仍可能生成不严谨代码,必须 Review |
综合来看,AI 编程工具在“提高初稿产出速度”和“辅助问题定位”方面价值明显,但在“业务正确性”“长期维护性”“工程安全性”方面仍不能替代开发者。
十五、最终结论
本次测评的核心结论是:AI 编程已经具备明显生产力价值,但更适合作为开发者的增强工具,而不是完全替代开发者。
它最适合以下场景:
- 快速生成样板代码;
- 编写 CRUD 接口;
- 生成基础单元测试;
- 解释错误日志;
- 编写脚本和命令;
- 小范围重构;
- 生成文档和注释;
- 辅助学习陌生框架。
它不太适合完全独立处理以下任务:
- 复杂业务规则设计;
- 高安全要求代码;
- 大规模架构决策;
- 性能敏感模块;
- 缺少上下文的历史项目维护;
- 无测试保障的自动修改。
对开发者来说,最合理的使用方式不是问 AI:“帮我完成整个项目”,而是把任务拆小,例如:
请只修改 app/crud.py,为 list_tasks 增加 completed 过滤参数,不要修改其他文件。
或者:
请根据下面的错误日志,只分析最可能的三个原因,并按优先级排序。
再或者:
请为当前接口补充 pytest 测试,只覆盖成功创建任务和查询不存在任务两个场景。
这样的提示更容易得到稳定结果,也更便于人工 Review。
总体而言,AI 编程的真正价值不是让开发者“少思考”,而是让开发者把更多精力放在需求判断、架构设计、代码审查和质量控制上。它可以显著减少机械重复劳动,但不能替代工程经验。未来的优秀开发者,很可能不是“会不会使用 AI”的区别,而是“能否正确指挥 AI、审查 AI、约束 AI”的区别。