ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

测试原理深度解析:第一性原理、金字塔与用例设计

测试原理深度解析:第一性原理、金字塔与用例设计 先聊聊测试原理这个系列。做测试这行久了你会发现一个特别有意思的现象很多人写了几年用例跑了几年回归但被问到“测试到底是在解决什么问题”时反而说不清楚。不是能力不够而是整个行业把太多精力放在了工具、框架和流程上却忽略了最底层的那套逻辑。这套逻辑就是测试原理。它不是某个工具的使用手册也不是某种流程的规范文件而是回答“测试为什么这么设计”“用例为什么这样写”“漏测到底漏在哪”的地基。这篇《测试原理一》先把最核心的东西讲透后续再逐步展开。这篇内容适合谁刚入行的测试新人、写用例写到麻木的功能测试、想转型测开的开发以及所有被“为什么我测了一堆却还是线上出问题”困扰的人。我会尽量用大白话把几个核心概念讲清楚再配上实际过程中踩过的坑和验证过的做法保证你能直接拿去做参考。1. 测试的第一性原理为什么测试永远无法证明“没有Bug”1.1 从一次线上事故聊起之前在一家电商公司有一次发版后半夜线上出了个大问题用户下单后支付回调丢失订单状态永久卡在“待支付”。数据修复花了整整两天还赔了一批优惠券。事后回溯负责的功能测试同学很委屈他说“我明明把支付流程从头到尾跑了一遍用例也全绿啊”。问题就出在这句“从头到尾跑了一遍”。他跑的是“正常支付成功”的路径而线上触发的是“支付回调丢失”这个异常路径。这个异常路径在需求文档里只有一句话“回调异常需处理”但没有人把它设计成用例。这就是典型的“测试通过”和“系统没问题”之间的巨大鸿沟。这个例子揭示了测试的第一性原理测试只能证明“系统在某些输入和场景下表现符合预期”永远无法证明“系统在所有情况下都是对的”。软件系统的输入空间是近乎无限的用户的操作路径、网络状态、数据组合、时序竞争这些东西组合起来是指数级的。你不可能全部测完也不需要全部测完但你必须清楚自己测到了哪里、漏掉了哪里。1.2 测试不是什么破除“证明正确”的迷思很多人潜意识里把测试当成“证明系统是正确的”这个任务。一旦抱着这个想法思路就会跑偏。你会倾向于挑那些“肯定能通过”的用例执行会下意识避开复杂的异常场景因为那些场景费时费力还可能暴露一堆bug导致发版延期。测完以后还要写一份“全部通过”的漂亮报告好像这样任务就完成了。但测试的意义恰恰相反。测试不是为了证明正确而是为了寻找“不正确”。一个测不出来的缺陷比一百个已经发现的缺陷更危险。已经发现的缺陷你知道它存在可以评估风险、安排修复而没测出来的缺陷就等着上线后让用户替你发现。你每写一条用例本质上都是在向系统提问“如果发生这种情况你会不会崩”用例设计得好不好不看你覆盖了多少“应该做的事”而看你逼问了系统多少“不该发生但可能发生”的穷途末路。所以我把测试的第一性原理总结成三句话测试是抽样不是全量。你要做的是用尽量少的样本覆盖尽量大的风险面。测试是证伪不是证实。你是在找系统的错不是在给系统的对做背书。测试是风险评估不是质量保证。测试能让你知道“这个版本还有哪些风险”而不能单方面保证“这个版本可以上线”。1.3 “可能出错的假设”才是测试起点这套逻辑落到实际操作上就变成了一个核心习惯每一条用例都必须源于一个“它可能会出错”的假设。比如一个登录功能你设计“正确用户名正确密码登录成功”这条用例背后的假设是“如果账号密码都对登录链路是不是通的”。但更关键的用例是密码错误时系统会不会提示错误、会不会记录失败次数连续输错多次账号会不会锁定、锁定后多久解锁账号被禁用、被删除、已过期分别是什么表现并发登录、异地登录、同一账号多端在线是什么行为每一条用例对应的都是一个潜在的错误假设。用例设计的过程本质上是把你对系统的“担心清单”变成可执行的验证步骤。水平高的测试和水平普通的测试差别不在于会用多少工具而在于脑海里能预想出多少个风险假设。2. 测试金字塔投入产出比才是核心考量2.1 金字塔模型到底在说什么Mike Cohn提出的测试金字塔几乎所有测试文章都会提到但很多人只记住了“底层单元测试多、上层UI测试少”这个比例却不太清楚背后的逻辑。金字塔从下往上分三层单元测试、服务/接口测试、UI/E2E测试。每往上一层测试的执行成本、维护成本、稳定性风险都在增加而测试能发现问题的定位粒度却在变差。也就是说UI测试发现一个bug你需要从页面一路往下排查单元测试发现一个bug基本直接指向某个函数、某几行代码。我见过不少团队把80%的用例都堆在UI层结果就是几个问题反复出现页面稍微改个文案一堆用例挂掉前端按钮位置微调回归脚本就要重写每次跑全量回归要四五个小时。团队天天修脚本比写脚本还累。这就是违背金字塔的代价。用一组我自己项目里的数据来说明同一个电商核心链路我分别在单元层、接口层、UI层各写了一批用例跑通同样的业务场景成本差异非常明显层级用例数平均执行时间环境依赖定位问题耗时单元测试1202秒无外部依赖分钟级接口测试458分钟需测试环境接口小时级UI测试181.5小时需完整环境浏览器天级2.2 金字塔比例就是风险策略为什么推荐单元测试占大头因为单元测试写得快、跑得快、失败了好修。这是第一层防线。在金字塔里越往下测试的成本越低反馈越快收益自然越高。接口测试是中间层验证的是系统间的契约。大部分线上故障其实出在接口层——字段为空、接口超时、返回值不兼容、鉴权不通过等等。这一层用例的价值在于它不依赖页面UI细节前端怎么改都不影响接口用例的执行稳定性高得多。UI测试在最顶层它的作用更像是“最后一道网”验证的是用户真实操作的流畅度比如注册流程、下单结算、支付跳转。这类用例数量要少而精只覆盖最有业务价值的几个端到端主流程。不要什么都往UI层塞。2.3 现实中的金字塔会变形理论和现实总有差距。很多团队做测试金字塔变形原因通常不是偷懒而是历史工程结构不支持。举个例子一个老系统后端代码几乎没有单元测试的土壤逻辑全部堆在Controller层一调就是一堆外部依赖。这种代码单元测试很难写因为根本不具备可测试性。这时候非得逼着团队写单元测试结果就是测了一堆空壳覆盖率上去了bug还是一个没少。我的建议是金字塔比例是可以调整的但调整必须是有意识的决策。如果单元测试确实写不动就加大接口测试的比例下沉那些本来应该在UI层执行的业务逻辑验证。重点是你要知道自己正在用接口测试的成本去弥补单元测试缺失带来的风险而不是想着“反正有UI回归不会出事”。3. 测试用例设计的底层逻辑不是凑数量3.1 输入空间划分与等价类测试用例设计有各种方法最基础、也最常用的就是等价类划分。这个方法的本质是把输入空间切成若干块认为同一块里面的数据系统的行为表现是一样的所以每块只需要挑一个代表值来测。举个例子一个限购功能的输入是“商品数量”规则是1到5件可以下单超过5件不能下单。输入空间可以划分为至少三个等价类有效数量1~5、无效数量小于1、大于5、非数字输入字母、特殊字符、空值。每个等价类挑一个代表数据来测就能覆盖大部分情况。但这里有个大坑划分等价类需要你足够了解需求。如果需求模糊划分出来的等价类可能是假的。我曾经遇到过一个需求说“金额超过1000需要走人工审核”但没说明“刚好等于1000”属于哪边。测试按两个等价类设计了用例“999自动通过”和“1001人工审核”结果1000这个边界值真实逻辑是取反了线上被用户试了出来。这就是等价类划分漏掉的边界。所以等价类必须配合边界值分析法一起用。边界值的基本思想是很多bug都出在边界附近。1~5的边界值是0、1、5、6这四个值必须单独测。1000的边界值是999、1000、1001三个都要覆盖。等价类帮你压缩测试规模边界值帮你守住最容易出事的那几道线两者组合才能达到“少而有效”。3.2 场景法从用户行为倒推用例等价类和边界值侧重的是“单个输入”但现实中的bug往往是多个条件和多个动作组合出来的。这时候就需要场景法。场景法核心是画业务流把用户从进入系统到目标完成的路径梳理出来然后找出基本流、备选流和异常流。以订单取消为例基本流用户下单 → 支付 → 发货 → 确认收货。备选流用户下单后未支付主动取消。备选流支付超时系统自动关闭订单。异常流用户支付成功后在发货前申请取消。异常流用户同时在不同设备发起取消和支付。每一条流都是一条或多条用例。场景法的好处是它能逼着你去理解整个业务逻辑的流转而不是盯着某个输入框抠细节。很多测试新手容易犯的毛病是场景法画出来的路径全是“顺利到达终点”的正向路径异常流只是象征性地补了一两条。这就是漏测重灾区。3.3 判定表与状态迁移对付复杂逻辑的两个利器当你遇到“条件很多、排列组合很复杂”的需求时判定表是最好用的工具。判定表的核心是把条件项和动作项拆开列出所有条件组合看每种组合下系统该做什么动作。举个例子一个订单需要满足三个条件才能自动发货已支付、库存充足、风控通过。三个布尔条件的组合就有8种每种组合对应发货、拦截或者人工处理的不同动作。用判定表列出来你会发现“已支付但库存不足”和“库存充足但风控拦截”这两个组合最容易在实现时被遗漏。状态迁移法则适用于另一类场景系统有明确的状态机比如订单状态、任务状态、审批状态。这种场景的关键是画状态迁移图列出所有状态之间的合法迁移和非法迁移。订单从“待支付”只能到“已取消”或“已支付”绝不能直接跳到“已完成”。测试时要把每个迁移路径都跑一遍尤其是非法迁移要确认代码层面做了拦截。4. 测试模型与流程V模型、W模型和敏捷测试4.1 V模型到底在强调什么讲测试流程绕不开V模型。V模型把开发和测试阶段对应起来左边是需求分析、概要设计、详细设计、编码右边是单元测试、集成测试、系统测试、验收测试。它最核心的思想是测试不是编码完成后才开始的活动而是从需求阶段就应该同步存在。这个思想你仔细品一下就会发现很多人做测试之所以效果差就是因为到了开发提测之后才开始看需求、写用例。此时需求已经做完了设计已经定稿了代码都写完了你再发现问题要么是需求本身就错了要么是设计已经跑偏了返工成本极高。V模型解决的就是这个问题需求分析阶段就该有测试人员介入搞清楚验收标准设计阶段就该根据设计产出测试方案编码阶段单元测试由开发自己写接口联调阶段做集成测试。层层对应每层都有“测试计划”提前准备。4.2 敏捷测试当“快速迭代”遇上“全面测试”现在的团队基本都在跑敏捷两周一个迭代需求三天一变。这时候再死守V模型就有点不合时宜因为V模型天然适合需求稳定的瀑布场景。敏捷环境下的测试最大的挑战不是“测什么”而是“怎么在两天内把一次版本改动的风险验证清楚”。敏捷测试的核心思路是把测试变成连续行为而不是一次性活动。需求拆卡的时候测试就参与进来把验收标准写在卡片上开发编码的时候测试把涉及本次需求的用例提前准备好开发提测后先跑一轮冒烟测试冒烟不通过直接打回。迭代结束后再补一轮全量回归保证历史功能没被破坏。这个模式执行起来有个关键要求用例库需要提前沉淀。如果每个迭代都是临时从零开始写用例敏捷基本玩不转。优秀的敏捷测试团队实际上有一个覆盖了核心业务链路的回归用例库每个迭代只需要增量补充新用例存量用例根据需求变化做微调即可。4.3 工作量估算测试不是“什么时候测完”而是“测到什么程度”流程层面还有一个常被忽略的坑——工作量估算。很多团队评估测试工作量标准是“功能点数量”然后按功能点给测试时间。但实际中有的功能100个功能点全是常规逻辑跑一遍就完有的功能10个功能点全是复杂状态机测试要画状态图、写覆盖矩阵、反复验证异常流。两者的工作量天差地别。我自己的估算方法是按“逻辑复杂度接口依赖数历史缺陷密度”三个维度来评估。逻辑复杂度看分支条件和状态流转的多少接口依赖数看调了几个外部服务历史缺陷密度看这个模块过去几轮迭代的bug率。三个维度综合打分再决定这个迭代投入几个测试人力、需要预留多少回归时间。这个方法比单纯数功能点靠谱得多。5. 测试质量度量覆盖率、漏测率与测试有效性5.1 覆盖率不是越高越好很多团队把代码覆盖率当成测试质量的硬指标比如“行覆盖率必须达到80%”。这个指标本身没问题问题在于大家对覆盖率的理解太粗暴。代码覆盖率分为行覆盖、分支覆盖、路径覆盖等。行覆盖是最容易达到的它只表示“这一行代码被执行到了”但不表示“这一行在不同条件下都被验证了”。举个典型例子一段代码有一个if-else分支测试用例覆盖了if为true的行整个函数行覆盖率达到100%但else分支从来没有执行过。一旦用户走到else分支bug当场暴露。所以看覆盖率更推荐关注分支覆盖率至少要做到核心模块的分支覆盖率达到合理水平。同时覆盖率数据要结合“哪个模块”来看核心交易、支付、库存模块的覆盖率要求应远高于普通查询接口。不要用平均值掩盖短板全系统60%覆盖率和核心链路80%覆盖率含金量完全不一样。5.2 从缺陷密度到漏测率覆盖率衡量的是“测了多少”但不直接衡量“测得好不好”。更直接的度量是缺陷相关指标常见的有缺陷密度每千行代码发现的缺陷数。这个指标可以和历史版本对比如果当前版本缺陷密度突然下降可能不是代码质量变好而是测试执行不充分。测试有效性开发自测发现的缺陷和测试团队发现的缺陷比例。如果开发自测能找到大部分缺陷说明开发自测质量高如果测试团队发现的缺陷只占总数的很小比例那测试价值就要打个问号。漏测率上线后用户或线上监控发现的缺陷除以总共应发现的缺陷。漏测率是测试团队最该关注的长期指标但计算它需要线上数据回流和缺陷复盘机制很多团队根本不做复盘所以漏测率永远是个谜。这里我建议大家不管项目多紧上线后的第一周一定要安排缺陷复盘。把线上发现的每一个问题拉出来回到测试用例库里去反查“这个场景为什么没测到”统计下来你很快会发现自己的盲区集中在哪一类——是异常场景想得少还是边界值漏了还是接口依赖没有模拟失败。这个循环只要坚持几个迭代测试用例的质量就会有肉眼可见的提升。5.3 测试报告怎么写得有用测试报告常见的问题是堆砌大量“已执行用例数”“通过率”“缺陷数”这样的数据但决策者看完仍然不知道能不能上线。真正有用的测试报告核心是回答三个问题这个版本的核心业务链路有没有完整验证哪几条是通过的哪几条是有风险的。遗留缺陷都是什么级别分别在哪些模块有没有绕过方案。经过测试后本版本剩余的主要风险点是什么。所以我现在写测试报告会把结论放在最前面版本是否具备上线条件。如果具备附带一句需要重点关注的地方。如果不具备明确列出阻断性问题清单。历史数据和过程指标放在报告尾部供后续统计使用。6. 常见问题与排查技巧实录6.1 测试同学常见的几个误区这些年在带团队和跨部门协作中我总结出几个反复出现的测试误区整理成一张速查表方便大家对照误区实际表现问题本质改进方向大包大揽型所有用例都调UI层一个页面改按钮位置回归脚本全挂违背测试金字塔按层级下沉用例报喜不报忧型报告里全是“通过”风险一句不提上线后出事才补复盘对测试定位认知偏差把风险可视化凑数型用例数量庞大一跑一整天但都是在重复验证同一条路径等价类划分粗糙做输入空间分析无脑追覆盖型为了覆盖率数据写一堆断言形同虚设的用例把指标当目标关注有效用例做分支覆盖测试开发对立型开发提测质量差测试测出一堆低级bug互相甩锅流程隔离提测标准前置定义6.2 排查线上漏测的实战方法如果你已经遇到线上缺陷怎么回溯改进我建议按下面几步走第一步拿到线上缺陷后先不急着骂人把它还原成一个具体的测试场景。线上用户是怎么操作导致出错的前置条件是什么数据是什么环境是什么第二步回到测试用例库搜反例。这个场景的用例就是缺失的还是不缺失但执行时被跳过了缺失的原因是什么边界值没分析到等价类划分错误需求都没写执行时被漏掉是为什么环境问题时间不够第三步把这次反查出的缺口补成正式用例并且加一条规则同类模块、同类功能在上线前必须逐条核对新增用例是否覆盖。第四步迭代结束后做一次汇总。你连续三个迭代漏掉的缺陷分布在哪里、属于哪一层、根因是什么。如果连续都是边界值问题那就针对团队做一次边界值设计培训如果都是异常流问题那就重点强化场景法的使用。这套复盘动作比任何测试工具都值钱。工具永远代替不了思考但复盘能让你每一次踩坑都转化成团队的测试资产下次迭代至少不会在同一类坑里反复跌倒。6.3 最后分享几个实操心得做测试久了我自己形成了一些固定习惯。写用例前一定先翻历史缺陷库看看这个模块过去半年出过哪些问题新用例必然覆盖历史缺陷场景。再一个是代码评审一定参加测试不参加代码评审会漏掉很多实现细节比如某个字段可能为null、某个接口可能超时这些在评审里一眼能看出来写用例时直接补进去。还有一个习惯是保留“探索性测试”时间。不管用例写得多全我始终会在测试计划的最后留出半天到一天做自由探索模拟真实用户乱点看系统能不能扛住。这个环节不需要写详细步骤但往往能发现自动化用例覆盖不到的真实用户行为问题。你可以理解成用例是守卫探索测试是巡逻兵。系统上线前两者缺一不可。测试原理这个东西说到底是告诉你“测试该怎么想”而不是“测试该怎么做”。“怎么做”的工具和框架会过时但“怎么想”的逻辑换多少种技术栈、多少种业务形态都适用。如果你能从这个系列里带走一个核心观念我希望是测试不是在为代码找理由而是在为风险画边界。你每画出一道清晰的边界系统就多一分底气。
返回列表