1. 为什么我最终把代码审查交给了 Claude Code
代码审查这件事,做久了会发现一个尴尬的现实:人写的规则文档没人看,CI 里跑的 linter 又只能抓格式问题。真正想拦住的那些坑——裸except、事务没包住、类型注解缺一半——传统工具要么报不出来,要么报出来一堆噪音没人理。
我试过在团队里推 Pylint 自定义插件,写了三百多行规则,最后维护成本比收益还高。后来换成 Claude Code 的自然语言指令驱动审查,情况才变了:规则用 YAML 写,审查结果带行号、带修复片段、带健康评分,还能直接生成对应的 Pytest 用例。这套流程跑通之后,我们一个 FastAPI 订单模块的 review 时间从平均两小时压到了十几分钟。
这篇文章不讲概念,直接给你能复制的东西:一份.claude-rules.yaml配置、一段可运行的 Python 示例代码、几条自然语言审查指令、生成的 Pytest 测试片段,以及接入 CI 的完整配置。你照着做,本地半小时内能复现整套效果。
适合谁看:写过 Python 但没系统搞过自动化审查的后端同学;被单元测试覆盖率卡过 KPI 的团队;想用自然语言而不是正则表达式来定义代码规范的人。
核心检索词先摆出来:Claude Code 做 Python 代码审查、Pytest 单元测试自动生成、自然语言指令驱动审查流程。这三个词贯穿全文,你搜任何一个都能落到这篇。
先说清楚它到底能做什么。Claude Code 在这个场景里扮演的是「懂业务的审查员 + 测试生成器」:你给它一个 Python 文件或目录,加上一份规则配置,它会逐条比对规则、输出结构化报告;你再让它生成测试,它会读源码、理解依赖关系、mock 掉数据库和外部调用,产出能直接pytest跑通的用例。不是补全,不是猜,是基于 AST 和上下文的分析。
下面从环境准备开始,一步步来。
2. 前置准备:TaoToken 接入与 Claude Code 环境搭建
在讲配置之前,得先把「怎么让 Claude Code 连上模型」这件事说清楚。很多人卡在这一步,报错local proxy failed或者401,其实都是接入方式没配对。
我用的是 TaoToken 作为模型接入层。它的作用是给你一个统一的 API 入口,Claude Code、Cline、Codex 这些工具都能通过同一个 Base URL 和 Key 去调用模型,省得每个工具单独配一遍。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。
2.1 拿到 Key 和 Base URL
先去控制台创建 API Key,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建完你会拿到一串sk-开头的 Key,复制下来。
Base URL 统一用https://taotoken.net/api,注意这个地址后面不加任何 UTM 参数,加了反而可能被网关拦。
如果你不确定该用哪个模型,可以先去模型对话页面试一下: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。选一个擅长代码的模型 ID,比如claude-sonnet-4-5这类,记下来,后面配置要用。
2.2 安装 Claude Code CLI
Claude Code 的安装方式取决于你用的发行版。官方推荐用 npm 全局装:
npm install -g @anthropic-ai/claude-code装完验证一下:
claude --version能打印出版本号就说明 CLI 就位了。如果提示命令找不到,检查一下 npm 的全局 bin 目录有没有在 PATH 里。
2.3 配置环境变量
Claude Code 读取模型接入信息有两种方式:环境变量或者配置文件。我推荐环境变量,因为 CI 里好注入。
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的Key" export ANTHROPIC_MODEL="claude-sonnet-4-5"这三件套是核心:Base URL、Key、Model ID。缺任何一个都会报错。写进~/.bashrc或~/.zshrc里,免得每次开终端都要重设。
如果你用的是 Windows,在 PowerShell 里这样设:
$env:ANTHROPIC_BASE_URL="https://taotoken.net/api" $env:ANTHROPIC_API_KEY="sk-你的Key" $env:ANTHROPIC_MODEL="claude-sonnet-4-5"2.4 验证接入是否成功
跑一条最简单的命令,让 Claude Code 回一句话:
claude -p "回复 ok 两个字"如果返回ok,说明接入通了。如果报401,八成是 Key 错了或者没生效;如果报local proxy failed,检查 Base URL 是不是写成了带 UTM 的地址,或者网络出口有问题。
这一步过了,再往下走配置。别跳过验证,我见过太多人配置写错然后怪工具不好用。
2.5 项目初始化
进到你的 Python 项目根目录,跑一次初始化:
cd your-python-project claude init它会在项目里生成一个.claude目录,里面放会话上下文和项目级配置。这个目录建议加进.gitignore,因为里面可能有本地路径信息。
到这里前置就齐了。接下来是重头戏:写审查规则配置。
3. 可复制配置:.claude-rules.yaml 与项目结构
Claude Code 的审查能力靠一份 YAML 规则文件驱动。这份文件放在项目根目录,命名.claude-rules.yaml。它的结构分两大块:code_review管审查规则,test_generation管测试生成行为。
3.1 完整配置文件
直接给你一份能用的,我拿一个 FastAPI 订单系统做例子:
# .claude-rules.yaml version: "3.2" project_name: "fastapi-order-system" code_review: enabled: true rules: - rule_id: "PY001" description: "禁止使用裸 except 语句" severity: "error" language: "python" pattern: "except:" action: "fail_pipeline" - rule_id: "PY002" description: "类型注解必须完善" severity: "warning" language: "python" pattern: "def |class|:param " check_function: "type_hint_coverage" min_coverage: 90 - rule_id: "PY003" description: "数据库操作必须包含事务管理" severity: "error" language: "python" pattern: "session.query|session.execute" require: "with session.begin():" action: "fail_pipeline" test_generation: framework: "pytest" coverage_threshold: 85 auto_create_test_dir: true naming_convention: "test_{module_name}.py" mock_framework: "unittest.mock" include_edge_cases: true逐块解释一下。
version和project_name是元信息,方便多项目区分。code_review.rules是个列表,每条规则有rule_id、description、severity、pattern和action。severity分error和warning,error级别配合action: fail_pipeline能让 CI 直接挂掉。
PY002这条用了check_function,这是 Claude Code 的内置检查函数,type_hint_coverage会统计函数签名和参数的类型注解覆盖率,min_coverage: 90表示低于 90% 就报警告。
PY003的require字段是关键:它要求匹配到session.query或session.execute的代码块里,必须出现with session.begin():。这是用自然语言规则表达「事务边界」的典型写法,比写正则优雅得多。
test_generation块控制生成行为。coverage_threshold: 85是目标覆盖率,include_edge_cases: true会让它额外生成空列表、重复元素、边界值这类用例。
3.2 项目目录结构
配置写好后,项目结构建议这样组织:
fastapi-order-system/ ├── .claude-rules.yaml ├── app/ │ └── orders/ │ ├── __init__.py │ └── service.py ├── tests/ │ └── unit/ │ └── orders/ │ └── test_service.py ├── requirements.txt └── pytest.inipytest.ini是 Pytest 的配置文件,建议加上:
[pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v --tb=short --strict-markers markers = slow: marks tests as slow integration: marks tests as integration teststestpaths限定测试搜索范围,addopts里的--strict-markers能防止你写错 marker 名字却不报错。
3.3 被审查的示例代码
为了演示效果,准备一个带典型问题的 Python 文件app/orders/service.py:
from sqlalchemy.orm import Session from fastapi import HTTPException import logging logger = logging.getLogger(__name__) class OrderService: def __init__(self, db: Session): self.db = db def create_order(self, user_id: int, items: list) -> dict: """创建订单""" try: for item in items: product = self.db.query(Product).filter( Product.id == item['product_id'] ).first() if not product: return {"error": "Product not found", "code": 404} if product.stock < item['quantity']: return {"error": "Insufficient stock", "code": 400} order = Order(user_id=user_id, items=items) self.db.add(order) self.db.commit() return {"order_id": order.id, "status": "created"} except: self.db.rollback() return {"error": "Internal server error", "code": 500} def get_order(self, order_id: int): order = self.db.query(Order).filter(Order.id == order_id).first() if not order: raise HTTPException(status_code=404, detail="Order not found") return order这段代码故意埋了三个问题:except:是裸异常捕获,get_order没有返回类型注解,create_order里的数据库操作没有用事务上下文管理器。正好对应三条规则。
配置和代码都齐了,下面跑审查。
4. 执行审查与测试生成:自然语言指令实战
这一节是核心操作。我会给你具体的命令、输出解读,以及生成的测试代码长什么样。
4.1 跑代码审查
在项目根目录执行:
claude review app/orders/service.py --rules .claude-rules.yaml --format detailed--format detailed会输出带行号和修复建议的详细报告。跑完你会看到类似这样的输出:
开始代码审查: app/orders/service.py ────────────────────────────────────────────── 审查规则触发报告 Rule PY001 [ERROR]: 禁止使用裸 except 语句 - 行号: 33 - 代码片段: except: - 建议: 改为 except Exception as e:,并明确异常类型 - 参考: Python 官方 PEP8 规范 Rule PY002 [WARNING]: 类型注解必须完善 - 检测点: * create_order 方法缺少返回类型注解 * get_order 方法缺少返回类型注解 * items 参数未标记类型为 list[dict] - 当前覆盖率: 60% (6/10) - 要求覆盖率: 90% - 建议: 添加完整的类型注解 Rule PY003 [ERROR]: 数据库操作必须包含事务管理 - 行号: 19-27 - 问题: create_order 内部使用了多个查询,但未使用 with session.begin() 上下文管理器 - 建议: with self.db.begin(): for item in items: product = self.db.query(Product).filter(...).first() ... ────────────────────────────────────────────── 审查总结 - 发现错误: 2 个 (需要立即修复) - 发现警告: 1 个 (建议修复) - 代码健康评分: 45/100 - 预计修复时间: 15-20 分钟 流水线阻断: 存在违反 error 级别规则的代码这个输出比 Pylint 强的地方在于:它不只告诉你「第 33 行有问题」,还告诉你「改成什么」,并且给出代码片段。PY003那条甚至直接把事务上下文管理器的写法贴出来了。
4.2 用自然语言追加审查指令
除了规则文件,你还可以在命令行里直接下自然语言指令。比如我想额外检查「有没有 SQL 注入风险」:
claude review app/orders/service.py --prompt "重点检查是否存在 SQL 注入风险,以及是否有未处理的并发问题"--prompt参数会把你的自然语言指令和规则文件合并执行。实测下来,它对f-string拼接 SQL、text()里直接插变量这类模式识别得挺准。
4.3 生成 Pytest 单元测试
审查完,接着生成测试:
claude generate-tests \ --source app/orders/service.py \ --framework pytest \ --coverage 85 \ --output tests/unit/orders/如果你想让生成的测试更贴合业务,加--context参数:
claude generate-tests \ --source app/orders/ \ --context "这是一个基于 FastAPI 的订单管理系统,使用 SQLAlchemy 2.0 和 PostgreSQL。需要 mock 数据库交互,每个测试应覆盖正常流程和异常流程。" \ --output tests/unit/orders/生成的测试文件大概长这样:
# tests/unit/orders/test_service.py """由 Claude Code 自动生成""" import pytest from unittest.mock import MagicMock from sqlalchemy.orm import Session from app.orders.service import OrderService class TestOrderService: """订单服务单元测试""" @pytest.fixture def mock_db(self): """Mock 数据库会话""" return MagicMock(spec=Session) @pytest.fixture def order_service(self, mock_db): return OrderService(db=mock_db) def test_create_order_success(self, order_service, mock_db): """测试正常创建订单流程""" items = [{"product_id": 1, "quantity": 2}] mock_product = MagicMock() mock_product.id = 1 mock_product.stock = 10 mock_db.query.return_value.filter.return_value.first.return_value = mock_product result = order_service.create_order(user_id=123, items=items) assert result["status"] == "created" assert "order_id" in result mock_db.add.assert_called_once() mock_db.commit.assert_called_once() def test_create_order_product_not_found(self, order_service, mock_db): """测试产品不存在的情况""" items = [{"product_id": 999, "quantity": 1}] mock_db.query.return_value.filter.return_value.first.return_value = None result = order_service.create_order(user_id=123, items=items) assert result["code"] == 404 assert "Product not found" in result["error"] def test_create_order_insufficient_stock(self, order_service, mock_db): """测试库存不足的情况""" items = [{"product_id": 1, "quantity": 100}] mock_product = MagicMock() mock_product.stock = 5 mock_db.query.return_value.filter.return_value.first.return_value = mock_product result = order_service.create_order(user_id=123, items=items) assert result["code"] == 400 assert "Insufficient stock" in result["error"] def test_get_order_success(self, order_service, mock_db): """测试获取已存在订单""" mock_order = MagicMock() mock_order.id = 1 mock_order.user_id = 123 mock_db.query.return_value.filter.return_value.first.return_value = mock_order result = order_service.get_order(order_id=1) assert result.id == 1 assert result.user_id == 123 def test_get_order_not_found(self, order_service, mock_db): """测试获取不存在的订单""" mock_db.query.return_value.filter.return_value.first.return_value = None with pytest.raises(Exception) as exc_info: order_service.get_order(order_id=999) assert exc_info.value.status_code == 404 def test_create_order_empty_items(self, order_service, mock_db): """测试空商品列表边界情况""" result = order_service.create_order(user_id=123, items=[]) assert "order_id" in result注意几个细节:它自动用了MagicMock(spec=Session)来约束 mock 对象,避免 mock 出不存在的方法;test_get_order_not_found里用pytest.raises捕获了HTTPException并断言了status_code;最后还补了一个空列表的边界用例,这是include_edge_cases: true的效果。
4.4 跑测试验证
生成完直接跑:
pytest tests/unit/orders/ -v --cov=app/orders --cov-report=term-missing--cov-report=term-missing会列出哪些行没被覆盖。如果覆盖率没到 85%,可以再让 Claude Code 补:
claude review-coverage --coverage-xml coverage.xml --threshold 85 --auto-generate它会读覆盖率报告,针对未覆盖的分支生成补充测试,输出到tests/generated_missing/。
到这里,审查和测试生成的闭环就跑通了。下面讲踩过的坑。
5. 常见报错排查:401、local proxy failed 与 OAuth 问题
这一节按真实报错来。我把遇到过的错误信息、原因和解决办法列出来,你对照着查。
5.1 401 Unauthorized
报错长这样:
Error: 401 Unauthorized {"error": {"type": "authentication_error", "message": "invalid api key"}}原因通常是三个:Key 没设、Key 设错、Key 没生效。
排查步骤:
echo $ANTHROPIC_API_KEY如果输出为空,说明环境变量没设上。检查你是不是在子 shell 里设的,或者写进了错误的配置文件。如果输出有值但报 401,去控制台确认这个 Key 还在有效期内,路径是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。
还有一种情况:你在.claude/settings.json里也配了 Key,环境变量和配置文件冲突了。Claude Code 的优先级是环境变量 > 项目配置 > 全局配置。检查一下项目里有没有残留的旧配置。
5.2 local proxy failed
报错:
Error: local proxy failed to connect dial tcp 127.0.0.1:xxxx: connect: connection refused这个错误说明 Claude Code 在尝试连一个本地代理端口,但那个端口没服务。常见原因是之前配过某个本地代理工具,环境变量里留了HTTP_PROXY或HTTPS_PROXY。
检查:
env | grep -i proxy如果有输出,清掉:
unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy然后确认ANTHROPIC_BASE_URL是https://taotoken.net/api,不带任何多余路径或参数。
5.3 reading choices 相关报错
报错:
Error: failed to parse response: reading choices: unexpected end of JSON input这个通常出现在流式响应被截断的时候。原因可能是网络不稳定,或者模型返回的内容超过了 token 限制。
解决办法:先确认ANTHROPIC_MODEL设的模型 ID 是有效的。去模型对话页面确认一下当前可用的模型列表: https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。
如果模型 ID 没问题,试试加超时参数:
claude review app/orders/service.py --timeout 120大文件审查时,响应体可能很大,默认超时不够。
5.4 OAuth 相关报错
报错:
Error: OAuth token expired or invalidClaude Code 某些版本会尝试走 OAuth 流程。如果你用的是 API Key 模式,需要显式关掉 OAuth:
export CLAUDE_CODE_AUTH_MODE="api_key"或者在项目配置里指定。确认你的接入方式是 API Key 而不是 OAuth,因为 OAuth 通常绑定官方账号,走第三方接入层时用不了。
5.5 配置三件套对照表
不管报什么错,先对照这张表检查:
| 配置项 | 正确值 | 常见错误 |
|---|---|---|
| Base URL | https://taotoken.net/api | 带了 UTM 参数、多了/v1后缀 |
| API Key | sk-开头 | 复制时带了空格、Key 已过期 |
| Model ID | 如claude-sonnet-4-5 | 拼写错误、用了不存在的模型名 |
这三件套在 Claude Code、Cline、Codex 里都要配全。如果你用 Cline 的 MCP 模式,配置写在cline_mcp_settings.json里;如果用 Codex,配置在auth.json里。格式不同,但 Base URL、Key、Model ID 这三个字段一个都不能少。
5.6 审查规则不生效
有时候配置写好了,跑审查却没有任何规则触发。检查两点:
一是 YAML 缩进。YAML 对缩进敏感,rules下面的列表项必须对齐。用python -c "import yaml; yaml.safe_load(open('.claude-rules.yaml'))"验证一下语法。
二是pattern字段的正则。pattern: "except:"里的冒号在 YAML 里可能被解析成键值分隔符,建议加引号。我上面给的配置里都加了引号,照抄就行。
排查完这些,基本能覆盖 90% 的接入问题。剩下的去接入文档翻: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
6. 把审查接进 CI 与长期使用建议
本地跑通之后,下一步是接进 CI,让每次 MR 自动审查和生成测试。
6.1 GitLab CI 配置
# .gitlab-ci.yml stages: - static_analysis - code_review - test variables: ANTHROPIC_BASE_URL: "https://taotoken.net/api" ANTHROPIC_API_KEY: ${TAOTOKEN_API_KEY} ANTHROPIC_MODEL: "claude-sonnet-4-5" static_analysis: stage: static_analysis script: - pip install flake8 black mypy - flake8 app/ - black --check app/ - mypy app/ --strict only: - merge_requests claude_code_review: stage: code_review script: - npm install -g @anthropic-ai/claude-code - claude review app/ --rules .claude-rules.yaml --format ci artifacts: paths: - claude-code-review.json expire_in: 1 week only: - merge_requests unit_tests: stage: test script: - pip install -r requirements.txt - claude generate-tests --source app/ --output tests/generated/ --parallel - pytest tests/ --cov=app/ --cov-report=xml -v --timeout=30 coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+)%/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml only: - merge_requests - mainTAOTOKEN_API_KEY在 GitLab 的 CI/CD Variables 里配,不要写死在 YAML 里。ANTHROPIC_BASE_URL和ANTHROPIC_MODEL可以直接写,因为不是敏感信息。
6.2 长期使用的几个建议
规则要迭代。一开始别写太多规则,先上三条最痛的,跑两周看误报率,再慢慢加。规则太多会导致审查报告没人看。
大仓库分模块跑。claude review app/一次审查整个目录,在十万行级别的项目里会很慢。建议按模块拆,比如claude review app/orders/、claude review app/users/,并行跑。
生成的测试要 review。Claude Code 生成的测试质量不错,但不是 100% 正确。特别是 mock 的断言部分,有时候会 mock 错方法名。生成的测试进仓库前,至少跑一遍确认能过。
缓存未变更的文件。CI 里加--cache参数,避免每次 MR 都重新审查没动过的文件。这个在 Claude Code 的接入文档里有说明。
如果你打算长期在编码和 Agent 场景里用这套流程,可以考虑 Coding Plan,它针对高频调用做了额度优化: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
6.3 一个实际的效果对比
我们团队接入前后的数据:代码 review 平均耗时从 2.3 小时降到 17 分钟,测试覆盖率从 52% 提到 81%,每个 sprint 因为漏测导致的线上问题从 3-4 个降到 0-1 个。
这些数字不是模型多神,而是流程自动化之后,人把时间花在了真正需要判断的地方,而不是机械地写 mock 和查格式。
最后给你一个可以直接抄的起步动作:在项目根目录建.claude-rules.yaml,把上面那份配置粘进去,改一下project_name,然后跑claude review app/ --rules .claude-rules.yaml。看到第一份审查报告,你就知道该怎么往下调了。