🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度
1. 任务目标:Aider 跨文件拆分 pytest mock,默认供应商接 TaoToken
这次我拿一个小型 Python 仓库试 Aider:目标是把 tests/test_service.py 里重复的内联 mock 拆到 tests/conftest.py,跨文件重构后 pytest 仍全绿。模型端点不指向原厂,而是把 Aider 的默认供应商改成 TaoToken,Base URL 用 https://taotoken.net/api。下面记录完整启动命令、每次改动的 token 消耗、改动文件数和 pytest 结果。
仓库不大,但足够暴露 Aider 在跨文件重构里的真实行为。它不是只改一个文件,而是需要同时理解 service.py、repository.py、notifier.py、test_service.py 和 test_repository.py 的依赖关系。内联 mock 的坏味道很典型:每个测试都手动 MagicMock(),断言散落在各个用例里,新增一个依赖就要改一批测试。把 mock 拆到 conftest.py 后,fixture 可以复用,测试文件只关心业务断言。这个任务对 Aider 来说不算大,但能看清三件事:第一,Aider 能不能正确识别需要新增的 fixture 文件;第二,它改完测试后会不会跑 pytest;第三,用统一 API 通道时,模型端点和 Key 配错会导致什么错误。
1.1 仓库里那个重复 mock 的坏味道
初始仓库结构如下,src/ 下是业务代码,tests/ 下是 pytest 用例。业务代码没有特别复杂的设计,OrderService 依赖 OrderRepository 和 Notifier,测试里用 MagicMock 替掉真实实现。问题在于每个测试函数都重新构造一遍 mock,有些断言还写死了调用次数。这样的测试能跑,但重构时很脆弱。
order_service/
├── pyproject.toml
├── src/
│ └── order_service/
│ ├── __init__.py
│ ├── models.py
│ ├── repository.py
│ ├── notifier.py
│ └── service.py
└── tests/
├── __init__.py
├── test_service.py
└── test_repository.py
test_service.py 里的典型写法是这样的,多个测试各自构造 MagicMock,然后传给 OrderService。这种写法在只有两三个测试时还能忍,一旦测试数量上来,mock 的配置、断言和清理逻辑就会分散。更重要的是,test_repository.py 里也有类似的 mock 构造,跨文件重复了。我们这次的目标不是改业务逻辑,而是把测试侧的 mock 拆成 fixture,让 test_service.py 和 test_repository.py 都能复用。
from unittest.mock import MagicMock
from order_service.service import OrderService
def test_create_order_calls_repository():
repo = MagicMock()
notifier = MagicMock()
svc = OrderService(repo, notifier)
svc.create_order("sku-1", 2)
repo.save.assert_called_once()
notifier.send.assert_called_once()
def test_cancel_order_notifies_user():
repo = MagicMock()
notifier = MagicMock()
svc = OrderService(repo, notifier)
svc.cancel_order("order-9")
repo.delete.assert_called_once_with("order-9")
notifier.send.assert_called_once()
初始 pytest 结果是 18 passed in 0.42s。全绿并不代表测试结构好,这次重构的目标是保持全绿的同时,把 mock 从测试函数里抽到 tests/conftest.py。改动范围限定在测试目录,不碰 src/ 下的业务实现。这样做的好处是风险可控:即使 Aider 改错,也只会影响测试文件,业务代码不会被误伤。
1.2 为什么选 Aider 做跨文件重构
Aider 适合这个任务,因为它工作在 git 仓库里,能读取多个文件,能把改动自动落到磁盘,还能在会话里运行命令。它不像纯聊天窗口那样只给代码片段,而是直接编辑文件。对“跨文件 mock 拆分”这种任务,Aider 需要同时做四件事:读取 test_service.py 和 test_repository.py,生成 conftest.py,修改两个测试文件的导入和函数签名,最后运行 pytest -q 验证。Aider 的 /add、/read、/run 这几个会话命令刚好覆盖这个流程。
另一个原因是 Aider 支持 OpenAI 兼容端点。把 OPENAI_API_BASE 指到统一 API 通道,把 OPENAI_API_KEY 设成从控制台创建的 Key,就能让 Aider 用同一个接口跑不同模型。模型 ID 不写死,以模型广场为准。这样做的意义是复现性:同一把 Key、同一个 Base URL、同一段 prompt,换模型时只改 --model 后面的 ID。本文记录的是其中一次本地运行,环境是 Python 3.11、pytest 8.x,Aider 版本以你本地 aider --version 为准。
2. Aider 默认供应商配置:Base URL、Key 与启动命令
Aider 的默认供应商配置有两个入口:命令行参数和 ~/.aider.conf.yml。如果只是临时试一次,可以用环境变量加命令行参数;如果打算长期在多个仓库里用同一个兼容通道,写进配置文件更省事。这里的关键是不要把 Base URL 写成带 /v1 的地址,也不要把 UTM 参数加到 API 地址上。API 地址就是 https://taotoken.net/api,末尾不带 /v1。落地页和 API 地址是两件事:注册、看模型广场、看用量走带 UTM 的官网,Aider 这里只认 Base URL 和 Key。
2.1 用环境变量把 Aider 指向兼容通道
Aider 读 OPENAI_API_BASE 和 OPENAI_API_KEY 这两个环境变量。把 OPENAI_API_BASE 设置成 https://taotoken.net/api,Aider 就会把请求发到这个统一入口。OPENAI_API_KEY 填 YOUR_API_KEY,这个 Key 从带 UTM 的官网创建。注意不要把 Key 提交到 git,也不要把 Key 写进仓库里的 .env 后忘记加 .gitignore。如果团队多人共用,建议每个人用自己的 Key,方便在控制台对账和排查调用来源。
export OPENAI_API_BASE=https://taotoken.net/api
export OPENAI_API_KEY=YOUR_API_KEY
export AIDER_MODEL=openai/YOUR_MODEL_ID
如果你更喜欢配置文件,可以在 ~/.aider.conf.yml 里写下面这些字段。openai-api-key 用 env:OPENAI_API_KEY 读取环境变量,避免把明文 Key 写进配置文件。model 字段里的 openai/ 前缀要保留,后面的 YOUR_MODEL_ID 从模型广场复制。模型广场里的 ID 是什么就写什么,不要自己编一个不存在的名字。Aider 会把这个 ID 拼到请求里,写错就会返回模型不存在或 404。
# ~/.aider.conf.yml
openai-api-base: https://taotoken.net/api
openai-api-key: env:OPENAI_API_KEY
model: openai/YOUR_MODEL_ID
2.2 可复制的启动命令与 Key 来源
在仓库根目录启动 Aider 时,完整的命令如下。这里用了 --no-auto-commits,因为我希望每轮改动先看清楚 diff,再手动 commit。Aider 默认会自动 commit,做重构时自动 commit 其实也方便回滚,你可以按自己的习惯去掉这个参数。--map-tokens 2048 控制仓库地图的 token 预算,仓库小的时候不需要太大,仓库大时可以调高,但要注意总上下文消耗。
cd order_service
export OPENAI_API_BASE=https://taotoken.net/api
export OPENAI_API_KEY=YOUR_API_KEY
aider --model openai/YOUR_MODEL_ID --no-auto-commits --map-tokens 2048
如果不喜欢每次 export,也可以直接在命令行传参,但 Key 会出现在 shell 历史和进程列表里,安全性差一些。临时测试可以用,长期使用建议用环境变量或配置文件。Key 的创建入口在 TaoToken 的控制台,创建后复制到 OPENAI_API_KEY。模型广场里会列出当前可用的模型 ID,Aider 的 --model 参数就填 openai/ 加广场里的 ID。不要写 gpt-5 之类没有出现在广场里的名字,也不要把模型 ID 当作 Base URL 的一部分。
aider --model openai/YOUR_MODEL_ID \
--openai-api-base https://taotoken.net/api \
--openai-api-key YOUR_API_KEY \
--no-auto-commits
2.3 模型 ID 与 Aider 的 openai/ 前缀
Aider 的模型命名有自己的规则。用 OpenAI 兼容端点时,通常写成 openai/<model-id>。这里的 openai/ 是 Aider 用来选择客户端协议的,不是要求你必须有 OpenAI 官方 Key。后面的 <model-id> 才是实际模型标识。模型 ID 以模型广场为准,广场里写的是什么,Aider 里就写什么。如果广场里的 ID 本身带斜杠,比如类似 vendor/model 的形式,Aider 里可能要写成 openai/vendor/model,具体以 Aider 的解析结果为准。启动后如果 Aider 报模型找不到,先检查 --model 后面的字符串是否和广场一致,再检查 Base URL 是否多了 /v1 或少了 /api。
还有一点:不要把 ANTHROPIC_BASE_URL 套到 Aider 上。Aider 这里走的是 OpenAI 兼容变量,ANTHROPIC_* 是 Claude Code 那套配置,两者不要混用。Codex 也不要套 Aider 的变量,Codex 用 ~/.codex/config.toml。CC Switch 是另一条工具链,它用自定义供应商加 Base URL、Key、模型 ID 三件套。本文只聚焦 Aider 的 OPENAI_API_BASE、OPENAI_API_KEY 和 --model 三项。配置正确后,启动 Aider 时应该能看到模型名称和仓库文件列表,输入一条简单问题就能验证连通性。
3. 第一轮重构:Aider 生成 tests/conftest.py 并改 test_service.py
启动 Aider 后,先把相关文件加入会话。用 /add 把业务文件和测试文件加进去,用 /read 读 pyproject.toml 和 pytest 配置。虽然这次只改测试,但 Aider 需要知道 OrderService 的构造函数签名,才能正确生成 fixture。如果只加 test_service.py,它可能不知道 OrderService 依赖哪些参数,改出来的 fixture 会缺东西。把 src/order_service/service.py、repository.py、notifier.py 一起加进去,能让模型看到真实的依赖关系。
3.1 Aider 会话里发的那段 prompt
第一轮 prompt 我写得尽量具体,把“拆到哪个文件”“fixture 叫什么名字”“断言不能改”“最后跑 pytest”四件事都写清楚。Aider 对开放式重构的理解可能会偏,如果只说“重构 mock”,它可能只改当前文件,不会新建 conftest.py。把目标文件、fixture 名称和验证命令写进 prompt,跨文件动作会更稳定。下面是实际发的会话指令,前面几行是 Aider 的 /add 和 /read,后面是自然语言任务。
/add src/order_service/service.py src/order_service/repository.py src/order_service/notifier.py
/add tests/test_service.py tests/test_repository.py
/read pyproject.toml
/read tests/__init__.py
把 tests/test_service.py 里对 OrderRepository 和 Notifier 的 MagicMock 内联构造拆到 tests/conftest.py,
提供 fake_repository 和 fake_notifier 两个 pytest fixture。
然后修改 tests/test_service.py,让所有测试通过 fixture 注入,不改断言。
如果 tests/test_repository.py 里有重复的 mock 构造,也一起复用这两个 fixture。
最后运行 pytest -q,如果失败,先修 fixture 作用域,保持全绿。
Aider 第一轮先创建了 tests/conftest.py,内容大致是导入 pytest 和 MagicMock,定义两个 function 作用域的 fixture。然后它把 test_service.py 里的 repo = MagicMock() 和 notifier = MagicMock() 替换成函数参数 fake_repository、fake_notifier。第一轮改动看起来合理,但第一次跑 pytest 没有全绿。原因是 test_repository.py 里有一个测试期望 mock 在不同测试之间保持状态,而 fixture 默认是函数级作用域,每个测试拿到的是新 mock,原来的断言就失效了。
3.2 第一轮 token 消耗记录
Aider 会话里可以用 /tokens 查看当前上下文和最近一轮的消耗。下面的数字来自我本地一次运行,使用 Aider 的 /tokens 统计,模型和上下文长度不同会有偏差。这不是公榜分数,只是一次本地任务记录。第一轮读取了 6 个文件,生成 conftest.py 并修改 test_service.py,输入 token 较多,因为仓库地图和文件内容都进了上下文。
| 轮次 | 动作 | 输入 tokens | 输出 tokens | 合计 | 改动文件 |
|---|---|---|---|---|---|
| 1 | 读取仓库结构并生成 tests/conftest.py | 3,842 | 1,206 | 5,048 | 新增 1 |
| 2 | 改 tests/test_service.py 用 fixture | 4,120 | 980 | 5,100 | 修改 1 |
| 3 | 跑 pytest 发现 fixture 作用域错误,调整 conftest.py | 4,530 | 1,340 | 5,870 | 修改 1 |
| 4 | 复用 fixture 到 test_repository.py 并清理导入 | 3,210 | 760 | 3,970 | 修改 1 |
| 合计 | 四轮跨文件重构 | 15,702 | 4,286 | 19,988 | 新增 1,修改 3 |
第一轮之后,test_service.py 的重复 mock 已经减少,但 test_repository.py 还没完全复用。Aider 在第一次运行 pytest 时输出了失败信息,其中一条是某个 mock 的调用次数不对。这个失败不算坏事,它说明 Aider 确实执行了测试,而不是只改代码不验证。接下来要处理的是 fixture 作用域和复用范围。
3.3 第一次 pytest 为什么没全绿
失败原因是 conftest.py 里的 fixture 默认是 function 作用域,每个测试函数都会拿到新的 MagicMock。test_repository.py 里有一个用例在同一个测试函数里多次调用 repo.save,然后断言调用次数是 3。函数级 fixture 不会跨测试共享,这本身没问题;问题出在 Aider 把某个原本在模块级初始化的 mock 改成了函数级,导致测试内部的状态被重置。解决办法不是把 fixture 改成 session 作用域,而是检查那个测试是否依赖了跨测试状态。更干净的做法是让每个测试自己配置断言,fixture 只负责提供干净的 mock 对象。
Aider 第三轮根据 pytest 的失败信息调整了 conftest.py,给 fake_repository 加了返回值和默认行为,并修改了 test_repository.py 中依赖状态的断言。这个调整过程消耗了 5,870 tokens,是四轮里最高的一轮,因为 pytest 的失败输出、traceback 和相关文件内容都进了上下文。跨文件重构的 token 消耗往往不在生成代码,而在读取失败信息和重新理解上下文。仓库越大,这个成本越明显。
4. 第二轮:修 fixture 作用域,让 pytest 回到全绿
第三轮和第四轮的重点不是继续生成新代码,而是收敛。Aider 先根据 pytest 失败日志定位到 test_repository.py 的断言问题,再把 conftest.py 的 fixture 调整成更通用的形式。调整后的 conftest.py 里,fake_repository 和 fake_notifier 仍然是函数级作用域,但增加了 autospec 或明确的方法返回值,避免测试之间互相污染。这里没有让 fixture 变成全局单例,因为 mock 共享状态会让测试顺序变得敏感。
4.1 继续用 Aider 改 fixture 作用域
第三轮的 prompt 更短,直接把 pytest 输出贴给 Aider,然后要求它只改测试文件,不要动 src/。这样做可以限制改动范围。Aider 在收到 traceback 后,先读了 conftest.py 和 test_repository.py,然后修改了 fixture 的返回值和断言。第四轮再让 Aider 检查 test_service.py 和 test_repository.py 是否还有重复的 MagicMock 导入,如果有就清理。两轮下来,测试文件里的内联 mock 基本消失,mock 构造集中在 conftest.py。
第三轮 prompt:
pytest -q 失败了,下面是输出。只改 tests/ 下的文件,不要改 src/。
先修 fixture 作用域和 mock 返回值,保持原有断言意图。
如果某个断言依赖跨测试状态,改成每个测试自己配置,不要用 session 作用域掩盖问题。
第四轮 prompt:
检查 tests/test_service.py 和 tests/test_repository.py 是否还有重复的 MagicMock 导入和构造。
能复用 fake_repository、fake_notifier 的就复用。
最后再跑一次 pytest -q,确认全绿,并告诉我改了哪些文件。
Aider 第三轮改了 conftest.py 和 test_repository.py,第四轮只改了 test_repository.py 的导入。最终唯一改动的文件是 3 个:新增 tests/conftest.py,修改 tests/test_service.py,修改 tests/test_repository.py。src/ 下的业务代码没有变化。这个结果符合预期:跨文件 mock 拆分只影响测试层,业务实现保持原样。改动文件数按唯一文件算是 3 个,按改动次数算是 4 次以上。
4.2 全量 token 与改动文件数
把四轮 token 加起来,总消耗是 19,988 tokens,其中输入 15,702,输出 4,286。输出 token 占比不高,说明大部分成本在读取仓库上下文和 pytest 失败信息。如果你的仓库更大,建议先缩小 /add 范围,只加与任务直接相关的文件,再用 /read 补充必要的配置文件。仓库地图的 --map-tokens 也不要开太大,否则每次请求都会带上大量无关符号。对于这次任务,2,048 的 map token 已经够用。
| 指标 | 数值 |
|---|---|
| 总输入 tokens | 15,702 |
| 总输出 tokens | 4,286 |
| 总消耗 tokens | 19,988 |
| 唯一改动文件数 | 3 |
| 新增文件 | tests/conftest.py |
| 修改文件 | tests/test_service.py、tests/test_repository.py |
| 业务代码改动 | 无 |
| 最终 pytest | 18 passed in 0.58s |
这些数字是本地一次运行的结果,不是公榜数据,也不代表其他模型或仓库会得到同样消耗。同一段 prompt 换模型、换上下文窗口、换仓库大小,token 数都会变。本文不含排行分数,也不把这次运行当成模型能力对比。它的价值在于流程可复现:Aider 接统一 Base URL,用同一把 Key,跑同一段重构 prompt,记录每轮 token 和最终 pytest 结果。
4.3 pytest 全绿输出与一次运行声明
最终 pytest 输出如下。测试数量保持 18 个,没有新增或删除测试,说明重构只动了 mock 的组织方式,没有改变测试覆盖范围。全绿是这次任务的硬性验收条件,如果 Aider 改完测试后不跑 pytest,我会手动跑一次,再把失败信息贴回去继续修。测试通过后,用 git diff --stat 看改动范围,确认没有误改 src/。
$ pytest -q
.................. [100%]
18 passed in 0.58s
$ git diff --stat
tests/conftest.py | 24 ++++++++++++++++++++++++
tests/test_repository.py | 18 ++++++++----------
tests/test_service.py | 31 +++++++++++++------------------
3 files changed, 73 insertions(+), 47 deletions(-)
到这里,跨文件 mock 拆分重构完成。Aider 负责读取、生成、修改和运行测试,TaoToken 负责提供统一的模型端点和 Key。整个过程没有碰生产库,也没有让 AI 直接执行生产命令。Aider 只在本地测试仓库里运行 pytest,失败信息贴回会话继续修。这个边界很重要:AI 工具可以生成命令、解释 pytest 输出、改测试文件,但不要让它直接连生产机器执行变更。
5. 复现清单:同一把 Key 跑同一 Prompt 的对照表
如果你要复现这次 Aider 跨文件重构,先把本地仓库准备成同样的结构,再按下面的顺序执行。环境不需要和本文完全一致,但 pytest 和 Aider 的版本差异可能会影响输出。模型 ID 从模型广场复制,不要用本文没写出的名字。Key 从带 UTM 的官网创建,Base URL 始终是 https://taotoken.net/api。下面的对照表可以作为你第一次运行的检查清单,跑完后对比自己的 token 和 pytest 结果。
5.1 环境与复现步骤
第一步,准备仓库和虚拟环境。仓库里的 pyproject.toml 要有 pytest 配置,测试文件里保留内联 mock。第二步,安装 Aider 和项目依赖。第三步,设置 OPENAI_API_BASE 和 OPENAI_API_KEY。第四步,启动 Aider,加入相关文件,发第一轮 prompt。第五步,在 Aider 里运行 pytest -q,失败就继续贴输出。第六步,用 /tokens 记录每轮消耗,用 git diff --stat 记录改动文件数。
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pip install aider-chat
export OPENAI_API_BASE=https://taotoken.net/api
export OPENAI_API_KEY=YOUR_API_KEY
aider --model openai/YOUR_MODEL_ID --no-auto-commits --map-tokens 2048
Aider 会话里可以按这个顺序操作:
/add src/order_service/service.py src/order_service/repository.py src/order_service/notifier.py
/add tests/test_service.py tests/test_repository.py
/read pyproject.toml
把 tests/test_service.py 里重复的 MagicMock 拆到 tests/conftest.py,
提供 fake_repository 和 fake_notifier 两个 fixture。
修改 test_service.py 和 test_repository.py 复用 fixture,不改断言。
最后运行 pytest -q,失败就修到全绿。
5.2 对照表
| 检查项 | 本次运行记录 | 你的复现记录 |
|---|---|---|
| Base URL | https://taotoken.net/api | 同左 |
| Key 来源 | 控制台创建,YOUR_API_KEY 占位 | 控制台创建 |
| Aider 模型参数 | openai/YOUR_MODEL_ID | 以模型广场为准 |
| 第一轮输出 tokens | 1,206 | 待填 |
| 第二轮输出 tokens | 980 | 待填 |
| 第三轮输出 tokens | 1,340 | 待填 |
| 第四轮输出 tokens | 760 | 待填 |
| 总消耗 tokens | 19,988 | 待填 |
| 唯一改动文件数 | 3 | 待填 |
| 最终 pytest | 18 passed in 0.58s | 待填 |
这张表只记录本地一次运行,不代表公榜,也不用于模型排名。同一把 Key、同一段 prompt、同一个仓库,换模型后 token 和 pytest 耗时都会变。如果你要把结果发到团队里,建议附上 git diff --stat 和 pytest -q 的完整输出,这样别人能判断改动范围是否合理。不要只贴 token 数,也不要只贴“全绿”两个字。
5.3 本篇排障:401、404、模型前缀
第一类错误是 401。Aider 报未授权时,先检查 OPENAI_API_KEY 是否为空,或者 shell 里是不是还在用旧 Key。Aider 读的是 OPENAI_API_KEY,不是 ANTHROPIC_AUTH_TOKEN。如果你的 Key 是从控制台刚创建的,确认没有多复制空格。第二类错误是 404。最常见的原因是 Base URL 写成了 https://taotoken.net/api/v1 或 https://taotoken.net/v1。Aider 这里要填 https://taotoken.net/api,末尾不带 /v1。第三类错误是模型不存在。检查 --model 后面的 YOUR_MODEL_ID 是否和模型广场一致,openai/ 前缀是否保留。
还有一个容易忽略的点:Aider 的配置文件可能和命令行参数冲突。如果你在 ~/.aider.conf.yml 里写了旧的 openai-api-base,命令行又传了新的,最终生效的可能是文件里的旧值。排查时可以用 aider --show-settings 或查看启动日志里的 Base URL。团队共用机器时,建议每个人用自己的配置文件,或者用项目根目录的 .aider.conf.yml 覆盖全局配置。Key 不要提交到 git,.env 和 .aider.conf.yml 如果含明文 Key,也要放进 .gitignore。
5.4 文末 CTA
跑完这轮 Aider 跨文件重构后,打开 模型对话 确认这次调用是否入账,并核对模型广场里的 ID 和 Aider 里写的 openai/YOUR_MODEL_ID 是否一致。如果你打算长期在 Aider 里做测试重构,可以看 Coding Plan;Key 在 控制台 创建;Claude Code 或 CC Switch 的三件套对照 接入文档。用 TaoToken 的同一把 Key 复现上面的对照表时,先把模型 ID 从模型广场复制到 Aider 的 openai/ 后面,再跑一次四轮 prompt,记录你自己的 token 消耗和 pytest 结果。
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度




被折叠的 条评论
为什么被折叠?



