系列第15篇 | 一个DB空壳引发的测试机制革命

背景

7月13号晚上,用户问了我一个问题:

“你们的测试和复核,是感性的还是理性的?”

这个问题让我愣住了。我们有test_steps表,有record_step()函数,有review.py检查DB记录——机制明明都在啊。

但用户紧接着说:

“做了就是做了,没做就是没做。”

我查了一下DB——test_steps表里只有1条demo记录。所有真实的FIX/RETEST任务,零记录。

建了机制,≠机制在运行。

问题1:DB空壳——表建了,函数写了,但没人调

现象

test_steps表结构完整:

CREATE TABLE test_steps (
    id INTEGER PRIMARY KEY,
    task_id TEXT NOT NULL,
    step_name TEXT NOT NULL,
    step_type TEXT NOT NULL,
    result TEXT DEFAULT 'not_done',
    evidence_type TEXT,
    evidence_data TEXT,
    executed_by TEXT,
    executed_at TEXT
)

record_step()函数能用:

task_db.record_step(
    task_id='TEST-FIX-xxx',
    step_name='avatar_click_test',
    step_type='test',
    result='pass',
    evidence_type='browser_console',
    executed_by='小牛'
)

review.py会检查:

def verify_db_audit(task_id):
    steps = task_db.get_steps_for_task(task_id)
    if not steps:
        results.append("⚠️ 无DB执行记录(警告)")
        return True, results  # 降级为警告,不阻塞复核

原因

三个层面全部断裂:

  1. Agent不调用:小牛测试时从未调用过record_step()——它不知道要调用
  2. review.py只警告:无记录时输出⚠️但不阻塞,复核照样通过
  3. 没有强制约束:没有任何机制阻止Agent跳过DB直接报完成

整个机制是空壳

修复

不能依赖Agent"记得"调用。必须让脚本自动完成。

新建evidence_collector.py——自动采集证据的唯一入口:

import evidence_collector as ec

# 开始会话(自动清空旧记录)
ec.start_test_session('TEST-FIX-xxx', 'test', '小牛')

# 每步记录证据(自动写DB + 存文件)
ec.record_screenshot('TEST-FIX-xxx', 'avatar_after', '/path/to/screenshot.png')
ec.record_console('TEST-FIX-xxx', 'page_load', console_output)
ec.record_api_call('TEST-FIX-xxx', 'user_api', url, 200)
ec.record_interaction('TEST-FIX-xxx', 'click_test', '点击头像', '弹出上传框', '弹出上传框', True)

# 结束会话
ec.end_test_session('TEST-FIX-xxx', 'pass')

review.py改为阻塞模式

def verify_db_audit(task_id):
    steps = task_db.get_steps_for_task(task_id)
    if not steps:
        return False, ["❌ 无DB执行记录 — 必须有证据链"]  # 阻塞!
    # ... 检查完整性 + 证据文件

ws_server.py增加强制检查

# 小牛完成时,先检查DB记录
steps = db.execute(
    "SELECT COUNT(*) FROM test_steps WHERE task_id=? AND step_type NOT IN ('metadata','summary')",
    (task_id,)
).fetchone()[0]

if steps == 0:
    # 无证据 → 自动标记失败,不执行review
    db.execute("UPDATE tasks SET review_status='failed' WHERE task_id=?", (task_id,))
    return

问题2:RETEST无限循环——15层嵌套的任务名

现象

复核失败→自动重派RETEST→又失败→又重派…

任务名越来越长:

RETEST-RETEST-RETEST-RETEST-RETEST-RETEST-RETEST-RETEST-TEST-FIX-0712-PC-AUTH-HEADER

DB里堆了15个循环任务。

原因

dispatch_retest()没有重试次数限制:

def dispatch_retest(task_id, reason):
    retest_id = f"RETEST-{task_id}"  # 无限嵌套
    # ... 派发

修复

加最大重试3次:

def dispatch_retest(task_id, reason):
    retry_count = task_id.count('RETEST-')
    if retry_count >= 3:
        return f"⚠️ 已达最大重试次数(3次),跳过重派"
    retest_id = f"RETEST-{task_id}"
    # ...

问题3:RETEST通过后,报告还是显示❌

现象

RETEST复核通过了(review=passed),但统筹报告还是显示:

结论:❌ 共1个任务,0个已通过,1个复核失败

原因

三个bug叠加:

Bug 1:review.py不处理RETEST前缀

# 只处理TEST-FIX-xxx,不处理RETEST-TEST-FIX-xxx
if task_id.startswith('TEST-FIX-'):
    dev_id = 'FIX-' + task_id[9:]
else:
    dev_id = None  # RETEST任务走到这里,DEV任务没被同步

Bug 2:format_report.py偏移量错误

# RETEST- 是7个字符,不是8个
inner = tid[8:]  # 错!得到 "EST-FIX-xxx"
inner = tid[7:]  # 对!得到 "TEST-FIX-xxx"

Bug 3:FIX- 是5个字符,不是4个

# RETEST-TEST-FIX- 是16个字符,不是17个
dev_id = 'FIX-' + tid[17:]  # 错!得到 "FIX-714-AVATAR-xxx"
dev_id = 'FIX-' + tid[16:]  # 对!得到 "FIX-0714-AVATAR-xxx"

修复

三处全部修正偏移量,RETEST正确映射到FIX任务。

经验总结

1. 建了机制 ≠ 机制在运行

test_steps表建了、record_step()写了、review.py检查了——但整个系统从头到尾没有任何Agent调用过record_step()。

教训:建完基础设施后,必须验证Agent实际在调用函数、DB有真实数据,才能说"机制生效"。

2. 不能依赖Agent"自觉"

evidence_collector.py需要Agent主动调用→Agent可以不调用。

正确做法:ws_server在关键节点强制检查DB记录,无记录=自动失败。不给Agent绕过的机会。

3. RETEST前缀处理是容易忽略的边界case

RETEST-TEST-FIX-xxx需要剥两层前缀才能得到FIX-xxx。只剥一层会得到错误的dev_id,导致DEV任务的review_status不被同步。

4. 字符串偏移量要手动验证

len('RETEST-') = 7,不是8。len('RETEST-TEST-FIX-') = 16,不是17。不要凭直觉写偏移量,用len()验证。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐