提出对AI生成的幂等性代码进行真实重放测试的方法,而非仅靠人工审查,能暴露顺序、Retry、碰撞处理中的边界问题。
判断一个 AI 生成的幂等层是否真正有效,最快的办法不是去读生成的代码,而是把它部署到一台临时的真实服务器上,然后开始重放请求——因为大多数问题藏在顺序、重试和冲突处理里,而不是语法层面。
幂等性常被当作一个二元属性来对待——要么一个处理器运行两次不会改变结果,要么不能——但在生产环境中,它更像一叠隐性契约的组合。处理器必须知道哪些字段参与身份键,当重试携带相同键但请求体不同时该怎么处理,超时后重试应该返回存储的结果还是再次执行操作,以及进程在两次请求之间重启时会发生什么。生成的代码往往满足了 happy path,却悄悄地在某个转换环节上编码了错误的假设,所以唯一可靠的判断方式就是让那个转换真实发生。
与其让模型解释为什么代码是正确的,不如让它在测试循环的边缘发挥作用。首先,让一个模型生成或审查一个小的 HTTP 服务,该服务在幂等键下处理扣款请求。然后将服务部署到临时服务器上——因为本地 mock 往往会隐藏真实的 HTTP 语义、进程生命周期,以及那种会让写得不完善的处理器困惑的网络重试。MonkeyCode 的免费服务器选项给了你运行这个服务的地方;它的免费模型访问则提供了第二个模型来产生对抗性的请求序列。披露:本文是作为 MonkeyCode 产品推广的一部分准备的。
从一个刻意简小的服务开始。下面的版本是说明性的,不是生产级的,且将状态保留在内存中,这样有趣的行为才能显现出来。
from fastapi import FastAPI
from uuid import uuid4
app = FastAPI()
store = {}
@app.post('/charges/{key}')
def charge(key: str, body: dict):
if key in store:
if store[key]['amount'] != body['amount']:
return {'error': 'key_conflict'}, 409
return {'id': store[key]['id'], 'amount': store[key]['amount']}, 200
record = {'id': str(uuid4()), 'amount': body['amount']}
store[key] = record
return record, 201
一个小的重放测试工具就足以把这些契约转化为通过或失败的信号。它为每个用例发送一系列请求,并在预期结果旁边打印出状态码和响应体。
import requests
BASE = 'https://your-throwaway-server.example'
cases = {
'duplicate_same_body': ('invoice-1', [{'amount': 100}, {'amount': 100}], [201, 200]),
'same_key_new_body': ('invoice-2', [{'amount': 100}, {'amount': 101}], [201, 409]),
'different_key': ('invoice-3', [{'amount': 100}, {'amount': 100}], [201, 201]),
}
for name, (key, bodies, wants) in cases.items():
print(f'--- {name} ---')
for body, want in zip(bodies, wants):
r = requests.post(f'{BASE}/charges/{key}', json=body, timeout=5)
print(r.status_code, r.json(), 'expected', want)
每一行都给你一个具体的契约。同一个请求体重复应该返回存储的结果并带有 200,不同请求体的重试应该返回 409,新键应该创建另一条记录并返回 201。如果生成的服务在第二个用例的第二次请求时返回了 201,你就找到了处理器把键的存在当作全部故事的那个确切位置。
前三个用例跑通之后,用请求 schema 提示免费模型,让它生成十个可能会让幂等端点困惑的更多序列。你通常会得到有用的类别:键末尾的空格、等效但类型不同的字段、客户端超时后的重试、一个重复请求在前一个请求完成之前就到达、以及在写入和响应之间的崩溃。只有当你能提前声明预期结果时,才将每个建议加入到测试工具中;没有清晰 oracle 的用例只会增加噪音。
大多数生成的实现都在 same-key-different-body 这个用例上失败,因为它们在内容之前先检查存在性。有些在重启时失败,因为内存状态消失,第二次重复会创建新记录。还有些在并发重复时失败,因为两个请求都miss了存储并同时创建了一行。顺序重放可能抓不到并发竞态,但可以在几秒内抓到前两个错误。
当免费服务器在重放过程中返回连接重置或 502 时,不要急于怪罪处理器;用本地实例重新运行用例,或等待一个稳定的窗口。免费服务器是一种便利,而非受控的基准环境,否则网络噪音会教你关于生成代码的错误教训。
如果操作涉及支付、库存或其他重复写入代价高昂的系统,这种方法不能替代事务级测试。在这些场景下,用生产环境中实际使用的数据库、隔离级别和锁定策略来测试,把临时循环仅当作早期的设计检查。同样,如果你的端点的幂等性依赖于数据库唯一约束而不是内存存储,那么上面的最小服务不会暴露约束冲突竞态。
修复处理器之后,把重放用例保存在代码旁边的文件中。下次模型改变逻辑时,先运行同一个文件。如果生成的 patch 无法通过之前失败的序列,你就有了比「代码看起来可疑」这个模糊感觉好得多的理由来拒绝它。