资讯详情 24-不会还有人把 E2E 当成单测换皮吧?没有归档就没有原料 | 执行器判定归档
📅 2026/10/31 1:59:06
上一篇不会还有人验收全靠点点点截图吧配置化断言才喂得回闭环 | YAML 测试不会还有人把 E2E 当成单测换皮吧没有归档就没有原料 | 执行器判定归档系列《从 Rule 到落地AI 工程化实践》第 24 篇标签E2E · 测试平台 · 判定 · 归档专栏从小白到AI落地大师序言YAML 用例会写了童鞋们想拉真链路。E2E 不是单测换皮真链路、真时序、真配置成本更高。本篇描述平台最小骨架执行器、判定策略、失败归档。用虚构名 e2e-harness。说句自嘲的我追求过「用例数量好看」关键路径却三天两头抖。后来砍到五条稳定关键路径信心反而上升。数量是虚荣稳定是睡眠。记住一句话执行器管跑、判定管色、归档管留证——而「留证」第一条纪律是隔离。上一篇teststation-lite里那个on_fail: capture: [logs/]把整个共享目录端走的坑本篇就当着你的面复现、再当着你的面修掉。下面这份e2e_harness.py纯标准库、复制即跑把「执行器 → 判定 → flake 归类 → 失败归档含隔离」一条链真实跑一遍。概念E2E端到端真链路测试。执行器拉环境、跑用例集、隔离与汇总。Verdict判定结果通过、失败、已知波动等策略输出。失败归档输入、日志、环境指纹等可回溯材料。可以理解为执行器是调度中心判定是裁判规则归档是黑匣子。没有黑匣子的赛场争议只能靠吼。组件图一张平台骨架就立住了failpass调度选套件 / 起夹具执行器跑步骤 / 隔离污染判定policy 评估归档meta stdout assert记忆层lesson_hint correlation_id红灯绿灯问题在哪边界不清把单元测试当 E2E或反过来用 E2E 冒充一切。判定靠口头例外「这条 flake 大家知道的」。失败不归档学习闭环断粮。稳定性工程缺位乱重试、共享脏数据、时间敏感断言满天飞——平台变成随机数发生器。举例执行器职责拉环境、跑用例集、隔离污染、汇总。判定策略显式全绿通过已知 flake 名单单独记账关键路径必须绿。归档留原始证据。这一整条链本篇不摆碎片直接搭一个能跑的e2e_harness.py。步骤总览下面这段代码一口气干了四件事执行器逐用例跑这里把结果简化为直接记录运行留痕统一写进一个共享logs/——这正是上一篇污染会长出来的温床。判定verdict关键路径红 → 整体fail且重试不豁免名单外失败 → 真红灯失败全在 flake 名单内 →pass。flake 归类classify同样是红按近况失败率分成real_fail / quarantine / flake / stable / too_new——进出名单靠算法不靠「大家都知道」。失败归档先复现上一篇capture: [logs/]端走整个目录的污染再用「一用例一沙盒」把它修掉最后落meta.json。判定策略配置代码里读的就是它policy:required_suites:[smoke_critical]# 关键套件红则整体红重试不豁免allow_flake:[ui_anim_03]# 已知波动单独记账不进豁免真红max_retries:1# 重试是止血不是隐瞒写小完整可跑e2e_harness.py# e2e_harness.py —— E2E 最小骨架执行器 - 判定 - 失败归档含隔离。承接 ep21 的 capture 污染伏笔并修掉# 纯标准库复制即跑。用法python e2e_harness.pyimportjson,shutilfromdataclassesimportdataclassfrompathlibimportPathdataclassclassCaseResult:id:strsuite:strpassed:boolcritical:boolFalsefailed_expect:strhistory:tuple()# 近 N 次 True/False供 flake 统计SUITE[CaseResult(adapt_smoke_01,smoke_critical,True,criticalTrue),CaseResult(adapt_forbid_no_sample,smoke_critical,True,criticalTrue),CaseResult(adapt_wrong_expect,smoke_critical,False,criticalTrue,failed_expectstdout_contains PERFECT_TOKEN,history(True,True,False,True,False,False)),CaseResult(ui_anim_03,visual,False,failed_expectscreenshot diff 0.02,history(False,True,False,True,False,True)),]POLICY{required_suites:[smoke_critical],allow_flake:[ui_anim_03],max_retries:1}FLAKE_RATE,WINDOW,MIN_RUNS0.3,20,5defverdict(runs,policy):判定关键路径必绿且重试不豁免名单外失败一律真红灯flake 名单只豁免已知波动。forrinruns:ifr.criticalandnotr.passed:returnfail,f关键路径{r.id}红重试不豁免real[r.idforrinrunsifnotr.passedandr.idnotinpolicy[allow_flake]]ifreal:returnfail,f名单外红灯{real}returnpass,全绿或失败全在 flake 名单内defclassify(case):flake 归类样本不足先观察在名单且失败率高隔离整改在名单且偶发已知波动不在名单真红。recentcase.history[-WINDOW:]iflen(recent)MIN_RUNS:returntoo_newratesum(1forpinrecentifnotp)/len(recent)ifrate0:returnstableifcase.idinPOLICY[allow_flake]:returnquarantineifrateFLAKE_RATEelseflakereturnreal_faildeftree(root):lines[]forpinsorted(root.rglob(*)):relp.relative_to(root)indent *(len(rel.parts)-1)lines.append(indentp.name(/ifp.is_dir()else))returnlinesdefmain():outPath(_e2e_out)ifout.exists():shutil.rmtree(out)out.mkdir(parentsTrue)# A. 执行器跑用例此处简化为直接记结果把运行留痕统一写进共享 logs/ —— ep21 污染的温床sharedout/shared_logsshared.mkdir()print( 执行器逐用例跑留痕写进共享 logs/ )forrinSUITE:(shared/f{r.id}.log).write_text(f{r.id}-{PASSifr.passedelseFAIL r.failed_expect}\n,encodingutf-8)print(f [{绿ifr.passedelse红}]{r.id}suite{r.suite}(ifr.passedelsef failed_expect{r.failed_expect}))# B. 判定一次评估定全局色v,whyverdict(SUITE,POLICY)print(f\n 判定verdict(policy) )print(f verdict{v}理由{why})# C. flake 归类同样是红红得不一样print(\n 判定失败用例的 flake 归类红不等于该删也不等于能豁免)forrinSUITE:ifnotr.passed:ratesum(1forpinr.historyifnotp)/len(r.history)print(f{r.id}:{classify(r)}(近{len(r.history)}次失败率{rate:.0%}, 在allow_flake{r.idinPOLICY[allow_flake]}))# D. ep21 的坑on_fail capture[logs/] 把整个共享目录端走 - 污染failednext(rforrinSUITEifnotr.passedandr.critical)pollutedout/capture_polluted/failed.idpolluted.mkdir(parentsTrue)forfinshared.iterdir():shutil.copy(f,polluted/f.name)p_filessorted(p.nameforpinpolluted.iterdir())print(f\n 归档捕获现场改前 vs 改后 )print(f [改前] capture[logs/] 端走整个共享目录 -{p_files})# E. 隔离修复每条用例独享自己的 box只 capture 自己boxout/isolated/failed.idbox.mkdir(parentsTrue)(box/f{failed.id}.log).write_text(f{failed.id}- FAIL\n,encodingutf-8)artbox/artifactsart.mkdir()shutil.copy(box/f{failed.id}.log,art/f{failed.id}.log)(box/lesson_hint.md).write_text(f#{failed.id}\n- failed_expect:{failed.failed_expect}\n- fault_signature: 审核时补\n,encodingutf-8)c_filessorted(p.nameforpinart.iterdir())print(f [改后] 一用例一沙盒只 capture 自己 -{c_files})print(f 消除的污染项{sorted(set(p_files)-set(c_files))})# F. meta.json字段为第 19/23 篇回流预留fixture_sha 呼应 ep21meta{run_id:2026-10-14/run-001,correlation_id:run-2026-10-13-a3,fixture_sha:f7c49c86,verdict:v,cases:[{id:failed.id,pass:False,failed_expect:failed.failed_expect,artifact_dir:fisolated/{failed.id}/artifacts/}],}(box/meta.json).write_text(json.dumps(meta,ensure_asciiFalse,indent2),encodingutf-8)print(\n 归档树下篇 lesson 原料从这长出来)forlineintree(out/isolated):print( line)if__name____main__:main()verdict的分支顺序是刻意的先扫关键路径红就直接判死且不进重试豁免——关键路径的绿是底线不能被「retry 三次终于绿了」蒙混。只有关键路径全绿才轮到「名单外真红」和「flake 名单豁免」两条。重试也别裸重加抖动防雪崩齐步打爆下游wait base * 2 ** attempt random.uniform(0, 1)。跑起来长这样python e2e_harness.py真实输出原样贴上 执行器逐用例跑留痕写进共享 logs/ [绿] adapt_smoke_01 suitesmoke_critical [绿] adapt_forbid_no_sample suitesmoke_critical [红] adapt_wrong_expect suitesmoke_critical failed_expectstdout_contains PERFECT_TOKEN [红] ui_anim_03 suitevisual failed_expectscreenshot diff 0.02 判定verdict(policy) verdictfail 理由关键路径 adapt_wrong_expect 红重试不豁免 判定失败用例的 flake 归类红不等于该删也不等于能豁免 adapt_wrong_expect: real_fail (近6次失败率 50%, 在allow_flakeFalse) ui_anim_03: quarantine (近6次失败率 50%, 在allow_flakeTrue) 归档捕获现场改前 vs 改后 [改前] capture[logs/] 端走整个共享目录 - [adapt_forbid_no_sample.log, adapt_smoke_01.log, adapt_wrong_expect.log, ui_anim_03.log] [改后] 一用例一沙盒只 capture 自己 - [adapt_wrong_expect.log] 消除的污染项[adapt_forbid_no_sample.log, adapt_smoke_01.log, ui_anim_03.log] 归档树下篇 lesson 原料从这长出来 adapt_wrong_expect/ adapt_wrong_expect.log artifacts/ adapt_wrong_expect.log lesson_hint.md meta.json输出你看什么一刀多响的判定四条用例里两条红但verdict只看第一条关键路径红就拍板fail理由写死「重试不豁免」。ui_anim_03也红可它不是关键路径——判定的优先级不是「红了几条」是「红的这条该不该要命」。同样是红红得不一样adapt_wrong_expect失败率 50% 但不在flake 名单 →real_fail真红灯得修代码ui_anim_03失败率一样 50% 却在名单里 →quarantine隔离整改别当稳定用例供着。classify用近况把「该删、该观察、该隔离、该豁免」分开记账取代「这条 flake 大家知道的」这种口头例外。上一篇的坑这一篇当面复现[改前]那行capture[logs/]端走了整个共享目录——四个用例的日志全被卷进adapt_wrong_expect的 artifacts。这正是 ep23 报告里那句「连同另外两条用例的日志一起进了 artifacts」的污染单跑没事套件一起跑就串味。隔离修复见效在文件名层面[改后]一用例一沙盒只 capture 自己那条 log消除的污染项明确列出被剔掉的三个别人的日志。隔离不是「少拷点」是每条用例只看得见自己的盒子——归档目录从此不会张冠李戴。归档树就是下篇的原料口artifacts/adapt_wrong_expect.log是现场、lesson_hint.md是给记忆层的草稿fault_signature: 审核时补——签名要人来定机器只留坑位、meta.json把correlation_id / failed_expect / artifact_dir串起来。第 25 篇「红灯喂回闭环」就从这棵树里取料。落盘的meta.json一次把回流要用的字段配齐fixture_sha呼应上一篇那 6 行夹具指纹复盘时对不上就是「你跑的不是你看到的夹具」{run_id:2026-10-14/run-001,correlation_id:run-2026-10-13-a3,fixture_sha:f7c49c86,verdict:fail,cases:[{id:adapt_wrong_expect,pass:false,failed_expect:stdout_contains PERFECT_TOKEN,artifact_dir:isolated/adapt_wrong_expect/artifacts/}]}最小里程碑先五条稳定关键路径再扩张。关联 lesson id 预留在归档里下一篇接回流才不痛苦。自检# E2E 骨架自检 - [ ] 能否画出平台组件图 - [ ] 是否有失败归档目录规范 - [ ] 判定策略是否显式含 flake 名单 - [ ] 关键路径是否优先稳定 - [ ] 归档是否预留 lesson 关联两问。第一口头例外写进策略了吗没写就还是口头。第二红灯目录能否让没参加现场的人复盘不能归档还只是日志垃圾桶。关键路径清单请贴在团队可见处。没有名单的「先稳定」会变成每人心里各有一条关键平台永远扩不准。max_retries 写小一点。重试是止血不是隐瞒不稳定。靠重试刷绿的 E2E是穿着西装的随机数。最小观测字段表贯穿编排 / MCP / 测试 / lesson后面排障请统一用这张表别各写各的字段名字段含义谁写correlation_id一次用户意图的全链路 id编排入口command/skill命中的路由路由层toolMCP 工具名协议层risk风险级Manifest / 工具元数据decisionallow / deny / confirm门禁test_case_idYAML / E2E 用例 id测试执行器lesson_id审核通过的教训 id记忆层日志里能靠correlation_id串起「谁点的菜 → 用了哪本手册 → 调了哪个工具 → 哪条测试红了 → 沉淀哪条 lesson」你才算有黑匣子而不是只有报警器。E2E 执行器最小职责拉起夹具 → 跑步骤 → 判定 → 归档原始输出 correlation_id 判定。没有归档的 E2E只是绿色的节日彩灯喂不回 lesson。归档目录建议按日切artifacts/2026-10-11/correlation_id/内含 stdout、判定 JSON、环境版本。留七天还是三十天按合规写进 README别靠磁盘喊疼时再删。E2E 不是单测换皮它跨进程、跨工具、跨门禁。单测保函数E2E 保路径。两条都要互相替代会瞎。读到这里不妨立刻做一个五分钟动作打开你们仓库找到与本篇对应的那个目录没有就新建把文中最小示意落成真实文件名。文件在讨论才有坐标文件不在下周还会从「我们觉得」开始。第 24 篇的验收不是「读完」是「仓库里多了一份可指出的东西」。举例加长判定 JSON 最小字段{ test_case_id, correlation_id, pass, failed_expect, artifact_dir }。缺artifact_dir人审只能靠想象复现。思考小结三句。E2E 平台先保关键路径稳定而非追求用例数量。失败归档是学习闭环的原料库。判定策略要显式避免口头例外。可以理解为黑匣子装好了再把红灯接进档案室。下篇从红灯到知识条目的自动与半自动路径并防假绿。下篇预告《不会还有人红灯只会重跑和甩锅吧假绿自动修是自杀 | 红灯喂回闭环》——假绿自动修是自杀式优化。觉得有用的话欢迎点赞、留言晒你的归档树有「用例很多关键路径很抖」的体感也欢迎对照里程碑拆。专栏从小白到AI落地大师《从 Rule 到落地AI 工程化实践》
相关阅读
电视盒子改 Armbian 服务器:三阶段跑起来,四项快赢
电视盒子改 Armbian 服务器:三阶段跑起来,四项快赢 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, …
2026/10/31 6:39:24 阅读全文 →