测试用例考古学:用Playwright和Tessy挖透遗留系统
接到那个项目的第一天我从运维手里接过一份被称为“唯一还活着”的文档——一张三年前的Excel测试用例表。点开一看用例编号到TC-471就断了后面全是(废弃)标注。代码库是典型的老中医式写文档的早走了注释里一半是乱码一半是前前任追星的碎碎念。在那一刻我突然意识到测试用例压根不是什么验收依据而是一根根能往地层深处戳的探针。这根探针戳下去戳出来的不只是bug还有人的选择、项目的旧伤、以及不少装着没看见的历史遗留问题。这就是我想在这篇文章里聊透的东西怎么用功能测试用例、单元测试用例设计方法、Playwright和Tessy这类工具把一套没人敢动的老系统挖个底朝天以及在这个过程中当探针底下突然露出一个“不能写进缺陷单”的秘密时一个QA到底该怎么选。这篇文章不打算讲纯理论我按自己实际操作过的三次“考古”经历来拆工具、模板、代码、伦理选择题全都有。1. 为什么测试用例能当“考古探针”1.1 探针原理用输入输出反推黑箱结构考古学里探针是个细长杆子打进土里不为了挖而是为了靠手感判断地下有没有空腔、墙体、墓道。测试用例在这套老系统面前干的就是同样的事系统还在跑但内部逻辑对你是半透明的你不需要完全读懂源码只需要构造一个输入观察输出然后跟“预期”比对就能知道这一层土底下到底是什么结构。关键是这个“预期”不能来自代码注释也不能来自过期的需求文档。我用的最简单办法是先写一个只记录不校验的冒烟脚本把所有核心入口带真实数据跑一遍把实际输出打印出来。第一轮你可能完全看不出对错因为你不知道“正确”长什么样但你会得到一个行为快照。第二轮开始你拿这个快照当基线改一个输入参数、切一个分支条件再看输出怎么变。变化就是线索不变也可能是线索——说明这个参数被某个历史分支吃掉了。这跟黑箱测试原理是一回事但心态完全不同。普通测试追求“验证需求是否实现”考古式测试追求“发现系统实际是怎么活的”。需求早就死了系统还活着你要探的是活物。从这个角度讲测试用例的断言就是你的探针触到目标层时发出的那声响——响得对你知道地层没塌响得不对底下八成埋着东西。1.2 现代QA工具箱为什么选这三件套我这次用的工具组合是Playwright、Tessy加Excel用例表对应功能回归、单元级验证和用例资产管理三个层面。选它们不是因为它们最新而是因为它们最适合当探针。工具/方法探测层级核心用途为什么适合考古Playwright端到端功能层给旧系统做行为基线自动等待稳、支持录屏和trace证据链完整Tessy单元/模块层C代码分支覆盖验证适合嵌入式老代码能精确到语句和分支Excel用例管理资产层记录用例、预期、证据、风险标志人人能读、可批量筛选、可导出评审这套组合的核心逻辑是Playwright负责告诉你“系统对外是什么样”Tessy负责告诉你“系统里面哪个角落是死的”Excel负责把这两层发现固定成团队能看懂的记录。以前我也试过用Selenium、Postman这类工具做类似的事但实测下来有两个问题——一是新工具对老浏览器的兼容性太差二是生成的报告只适合自己看不适合拿去做评审证据。Playwright的trace文件可以直接发给开发人家看一眼时间轴和网络请求就知道问题出在哪这一点在推诿扯皮的环境里太重要了。另外提一句测试用例模板在考古项目里别用公司通用的那种“冒烟测试模板”“验收测试模板”。我后面会专门讲怎么改模板字段这里先记住一个原则考古用的用例模板核心字段不是“预期结果”而是“实际行为”和“偏差分析”。2. 考古第一层功能测试用例的“地表测绘”2.1 用Playwright给旧系统建立行为基线地表测绘这步说白了就是先别急着找茬先把整片区域走一遍记录成图。我第一次接触那套老系统时连登录后到底跳转到哪个页面都拿不准文档说跳工作台浏览器实测跳到了一个叫“中转页”的东西。这不是bug只是没人更新文档但对于后来的所有用例这个中转页就是真实的地表特征不是错误。我写了下面这组Playwright用例刻意把所有断言都写得“宽”——先只断言URL模式其他的全部输出日志import { test, expect } from playwright/test; test.describe(遗留系统地表测绘, () { test(登录后实际跳转路径记录, async ({ page }) { // 用环境变量注入账号避免凭据进代码库 const username process.env.PROBE_USER ?? qa_probe; const password process.env.PROBE_PASS ?? ; await page.goto(https://legacy.example.com/login, { waitUntil: domcontentloaded }); await page.fill(#username, username); await page.fill(#password, password); await page.click(button[typesubmit]); // 只等待跳转不预设一定是 /dashboard await page.waitForURL(**/*, { timeout: 10000 }); console.log(实际跳转URL:, page.url()); console.log(页面标题:, await page.title()); // 记录所有可见按钮文本用来反推功能入口 const buttons await page.locator(button, .btn, [rolebutton]).allTextContents(); for (const b of buttons.slice(0, 20)) { console.log(可见操作:, b.trim()); } }); });这段代码看着简单但它是整个考古项目的第一个工具。跑完之后我拿到了一份“系统实际入口清单”里面有三个按钮的名字跟需求文档完全对不上。比如有个按钮叫“重新同步”文档里写的是“修复数据”。我当时就意识到文档描述的可能是三年前的逻辑而按钮文案暗示这套系统经历过一次不彻底的重构。实际操作中有几个坑要提醒。第一不要在旧系统上一上来就用Page Object Model那一套太重的封装会在你还没摸清页面结构时拖慢你。第二Playwright的自动等待默认很激进老系统接口常常要十秒才响应建议把timeout调大并且在关键操作前用waitFor而不是expect。第三所有探针脚本务必能从命令行传参因为旧系统的环境往往不止一套你总得在测试环境、预发环境各跑一遍。2.2 功能测试用例模板的“探方设计”考古不是乱挖要有方格网。Excel里的测试用例模板就是我的方格网。公司里常见的那种模板字段是用例编号、模块、前置条件、测试步骤、输入数据、预期结果、实际结果、执行人。这套字段对付“验证有没有实现”够用但对付“探明系统长什么样”太单薄。我给老系统项目设计的模板长这样字段名填写说明考古用途用例编号TC-AR-001探方编号探针目标本次想摸清的行为/接口明确探测意图输入扰动相对基线的改动参数/顺序/状态记录变量实际行为系统真实反馈地层记录预期依据文档/代码/历史工单判断偏差来源偏差分析行为与依据的差异确认疑似文物风险标记高/中/低决定是否下钻证据链接截图/trace/日志路径后续评审材料这个模板里最反直觉的是“预期依据”。普通用例模板里的预期写的是“正确结果”但考古场景下你根本没有标准答案。所以我把“预期”改成了“依据”——你预期它应该怎样是基于文档还是基于代码注释还是基于“我猜的”。如果三条依据互相打架这个用例本身就是一个重大发现哪怕系统行为看起来再怎么正常。给老系统写快照用例我总结出三条经验。一是每个用例只做一个扰动别摸着摸着把三个变量一起改了到时候出了偏差根本没法归因。二是先写“地层基线”用例再写“扰动用例”基线用例锁死当前行为扰动用例负责探索差异。三是表格里必须留一列叫“重复执行稳定性”因为老系统经常有偶发问题一个用例跑三次结果不一致这本身就是一条需要上报的高价值发现。3. 考古第二层单元级测试用例设计方法与Tessy实践3.1 单元测试用例设计方法从业务场景钻到代码结构功能用例探到表层后我已经锁定了好几个“可疑地层”。这时候要继续往下钻就得进单元级。单元测试用例设计方法的核心是等价类、边界值、判定表、路径覆盖这四板斧但在考古模式下每一板斧问的问题不太一样。等价类划分我用来回答“哪个输入类别的行为跟文档描述不符”。边界值用来找“刚好卡在阈值上的历史特判”——很多老系统都留着这种特判某个参数等于0时走A逻辑等于1时走B逻辑中间一概当异常。碰到这种代码你几乎可以断定当年有人做了个紧急修复然后没补注释也没补测试直接把系统留给后来人考古。我印象最深的一次是用路径覆盖挖出了一个“永远不可达”的分支。那个模块是个C语言写的鉴权函数长约两百行Tessy跑完之后报告显示有一行代码的覆盖率始终是0。我一开始以为是桩函数配得不对查了半天才发现那行代码所在的分支前面有一个逻辑嵌套父条件是if (flag 2)但flag在进入这个包时已经被强制置为1了。换句话说这段代码从三年前一次“临时改动”开始就再也没被执行过但它的存在一直在虚耗后来的维护者。3.2 Tessy测试用例与Excel表的管理实践Tessy是嵌入式领域常见的单元测试工具尤其在汽车电子和通信设备这类对代码覆盖率有硬性要求的行业里会碰到。它用起来有个特点测试用例可以手工在工具里录也可以从Excel导入导出。很多QA一听到“代码覆盖率”就以为这是开发的事其实Tessy用得好不好关键就在于用例设计那一环。我用Excel做Tessy用例的标准表头如下Excel列对应Tessy概念填写要点TestCase_ID用例唯一ID推荐M_[函数名]_[序号]Function被测函数必须带模块前缀输入参数实参/桩值结构体参数要逐字段列清楚Stub返回值桩函数行为区分“返回默认值”和“返回指定值”预期输出期望返回值/被修改参数写清精度和范围覆盖目标语句/分支/MC/DC对应Tessy里的CoverageGoal依赖条件全局变量/静态变量初始化这是最容易被忽略的Excel和Tessy的往返同步是个实操痛点。我的做法是先导出一份干净的模板在Excel里批量填好用例然后导入Tessy跑覆盖跑完后把覆盖率结果再导回Excel的一列“实际覆盖率”。这里有个细节结构体输入参数在Tessy里展开成很多行直接在Excel里手写很累我一般用公式生成struct.field的完整路径再配合批量填充效率能提不少。这次考古我根据函数行为把鉴权模块的用例设计成了几张判定表输入用户类型内部/外部/第三方与资源等级机密/普通/公开的九种组合时间戳参数取今天、去年、1970年、2038年四个边界值每个分支至少一条“真”路径和一条“假”路径用于路径覆盖判定表设计完再映射成Excel用例Tessy就能一次跑完。有一个用例输出了一个让我起疑的结果传入一个早已废弃的“资源类型9”时函数居然返回了“机密级别”。文档里根本查不到这个枚举值顺着代码一翻才发现结构体里有一个没人引用的联合体字段里面埋着旧版的加密接口。这件事后来被证明是一个数据兼容性隐患但它不是功能用例能发现的——功能层根本不会走到这个废弃联合体只有单元级的探针才能触到。4. 考古第三层探针刺破地层碰到“伦理困局”4.1 发现了前辈们知道但闭口不谈的隐患当你挖到这一步真正的困局才开始。我在那套老系统里挖到了一段状态机代码功能用例显示它的某个状态分支在线上永远跳不进去。测了三天我确认这不是环境问题于是翻历史工单翻到三年前的一条记录里面有句话写得很隐晦“因业务原因该分支暂不启用已通过配置规避”。我当时心里咯噔一下。这不是一个普通的历史遗留问题这是有人“知道问题存在但选择了绕过”。我拿着这个分支的复现路径去找负责的老开发对方一句话就让我愣住了“这个分支别测了测出来大家都不好看。”我后来才明白那个分支如果被触发会导致一个极端情况下数据错乱但触发概率低、业务影响面小三年前的团队决定不修只是用配置把它绕开了。测试用例一旦把这条路径打通等于把当年那个“心照不宣”的决定翻到桌面上。这里我做了个选择我把三种方案摆在脑子里比了一遍方案做法风险我的判断沉默不报把用例删除当没看见隐患埋着未来出事责任在测试不可取私聊相关人员口头告知不落文档问题不被重视也不会被记录治标不治本按缺陷流程上报写缺陷单附证据链会议评审可能得罪人但事实清楚我选择了这个我当时的做法是先把复现路径写成一份最小化用例附上三年前的工单截图、当前的覆盖率报告、以及线上配置对比然后在缺陷评审会上说了一段话“这个分支现在不是bug因为配置绕开了它但如果配置被改或者数据量达到某个阈值它会成为线上故障。修复成本是X人日不修复的风险是Y请业务和开发一起拍板我按结论走。”这是关键一步QA的伦理困局往往在于“测试发现了东西但没人授权你处理它”。我的原则是我不替任何人做业务决策但我绝不让决策者在不了解事实的情况下做决策。把证据链交出去把选择权交出去剩下的责任归属就清楚了。4.2 “覆盖率必须百分百”的形式主义陷阱考古行动进行到一半领导发了一个指令单元测试分支覆盖率必须达到100%。这个指标看起来很漂亮但做过老系统的人都知道硬指标催生出来的往往是数字游戏。我见过一种特别坑的做法就是给那些“永远不可达”的分支强行加一个空断言或者用一个假的输入直接跳过判断。覆盖率报表上从95%涨到100%但真实的质量一根毛都没提升。更麻烦的是这种“为了指标凑覆盖”的用例会污染整个测试资产等你真正想用这些用例做回归时会发现它们全是占位符。我的处理方法是做两套统计。一套叫“声明覆盖”就是报表上给领导看的覆盖率我如实统计Tessy跑出的数据。另一套叫“验证覆盖”把那些真正有断言、有输入扰动、失败了能定位问题的用例单独汇总。声明覆盖是100%验证覆盖可能只有83%。我会把两套数字都放在周报里用一行字说明差异来源。这不是阴阳怪气而是让数据自己说话差的那些分支是因为“绕开配置”或“废弃代码”没法被测。这里有个很实用的技巧给每一条“纯占位”用例在Excel里加一列“是否有效验证”只有标注为“是”的用例才进入缺陷关联和回归分析。这样领导看到的是完整的数字但团队内部有另一套准确的地图。别小看这一列后来好几次风险评审我都是靠这列数据挡住了“盲目冲指标”的决策。4.3 QA的边界不当道德警察也不做装睡的人考古挖到深处很多新人会陷入两个极端。一个极端是把自己当成道德警察觉得发现了历史问题就必须“昭告天下”跟团队闹得很僵另一个极端是选择装睡用例发现了问题也不报因为“别人都不管我干嘛管”。这两个极端我都见过也都踩过。我的体会是QA的职责边界很清晰但执行起来需要技巧。第一你不是产品经理所以不用决定要不要修、什么时候修这个问题请丢回给业务和开发。第二你不是背锅侠你的职责是“证明事实存在”至于“为什么存在”那是开发要解释的。第三你也不是透明人你不能在已经掌握证据的情况下把风险吞进肚子里。我后来总结出一套“翻译话术”专门用来把技术问题变成业务能听懂的语言。比如对付那个状态机分支我不会说“路径覆盖率和判定表覆盖率不一致”我会说“线上如果出现某种配置组合这里的数据可能错乱最坏情况影响大约涉及X个用户修复预估需要2天。”开发反驳的时候我手里有Tessy报告、Playwright录屏、Excel历史工单对照表每一样都是带时间戳的证据。我记得那次评审会结束后开发组的组长私下来找我说了一句我一直记到现在的话“要是早有人像你这么测这个坑三年前就该填了。”那句话不是说我的技术多厉害而是说很多坑不是不能填是没人把它挖出来放到阳光下。QA的价值从来不是会写几个漂亮用例而是敢把真相摆到桌面上并且把话说得让人无法忽视。5. 避坑清单与复盘让考古成果变成团队资产5.1 遗留系统自动化的五个典型坑考古探针要有效探针本身得稳定。我在老系统上跑Playwright和Tessy时踩过高频的五个坑值得单独列一下过度依赖自动等待老系统的接口经常三五秒没响应Playwright的自动等待一旦超时就重试结果把偶发慢接口当成了失败。解决方式是给核心接口单独设置test.setTimeout(15000)并区分“超时失败”和“断言失败”。iframe和动态元素老系统特别喜欢用iframe包旧功能页面Playwright默认只能访问主frame必须手动page.frameLocator()切换。还有一类元素是点击后才动态生成的要先触发事件再定位。环境依赖太强老系统的测试环境连数据库、缓存、文件服务全靠一套共享环境自动化跑着跑着就被别人的数据搞挂了。后来我给所有探针用例都加上“数据指纹”前缀比如用户名后面拼时间戳。Trace文件爆炸Playwright的trace录多了以后磁盘占用会非常大。考古阶段我建议只对可疑用例开启trace别对全套用例开。用例太脆一个用例里写了十几个断言最后根本无法定位失败原因。考古用例坚持“一扰动一断言”断言失败后先看日志而不是先改用例。5.2 用例评审让开发承认这是bug的三板斧考古发现的价值最终要靠缺陷评审会来确认。怎么让开发在评审会上认下这个“历史坑”我有一套三板斧流程先给复现录屏不要一上来就丢代码片段或Excel表格先放一段Playwright的trace回放或录屏让全场看到“这个分支真的能走到”。录屏十五秒的效果胜过口头辩论半小时。再给最小用例把问题简化成最快的复现路径最好是一段能从浏览器控制台或Tessy单独跑的用例。开发不需要理解你的整个测试框架他只需要在自己的环境里跑一遍就能看到现象。最后给影响分析说清楚“现在不修什么情况下会爆爆了影响多大”。这里就用上Excel用例表里的“风险标记”列把高、中、低风险各列成一个清单让业务方在知情的前提下表态。缺陷标题也有讲究。我见过写成“某某页面报错”的缺陷单开发看半天不知道要干什么。合格写法最好是一个公式[模块] [条件] [实际表现] [期望表现]。比如“[状态机] 当资源类型为9时鉴权返回机密等级预期应拒绝访问”。这种标题信息量足够开发一看就知道问题的性质。5.3 从用例到知识库考古结束时留些什么考古项目收尾时除了缺陷单和测试报告我会额外做一件长期有用的事把整个探测过程整理成一份“系统地层图”文档沉淀到团队知识库。这份文档不是测试报告里面写的不是“本次发现了几个bug”而是“这个系统有哪些地方是活的、有哪些是历史遗留的、哪些是刻意绕开的”。具体内容包括三层第一层是功能入口实测清单标注哪些入口是文档没写的第二层是模块级的地示意图列出每个C函数里“永远不可达”的分支和废弃结构体第三层是“已确认但暂缓处理”的历史隐患清单每条都要有决策时间、决策人、风险描述。做个十几分钟就能把这份文档做完但它对后来接手的人价值极大。很多新人在老系统面前手足无措不是因为技术不够而是因为没有这份“地层图”每踩一种土都得从头猜。后来我养成了一个习惯每次写完一个测试用例都会问自己一个问题——“如果这个用例失败了它能告诉我到底是哪个环节出了问题吗”能回答的才配叫测试用例回答不了的它只是一个占位符。考古探针的本质不在于你戳了多少个点而在于每个点戳下去之后能不能精准定位到那层真正有问题的地方。QA这个岗位不太会出“孤胆英雄”但我们手里有探针有证据有把问题摊开来说的底气。这套东西用熟了哪怕眼前的代码再烂、文档再稀烂你也总能找到第一个可以撬动的地方。