“ceshi”这个名字看起来有点潦草,其实是我早年一个内部项目的代号。当时团队急着赶版本,谁也没心思给项目起个正经名字,随手就打上了“测试”的拼音。但这个随手命名的项目,最后却成了我梳理整套测试流程的起点。做测试这件事,很多人觉得就是“点一点、看看有没有报错”,可真把一个测试项目从头做到尾,你会发现它远比想象中复杂——要拆需求、设计用例、搭环境、做回归、写报告,任何一个环节偷懒,上线之后都会用事故的方式还回来。
这篇文章我想用“ceshi”这个项目作为例子,完整讲讲我是怎么做测试的:怎么把需求拆成可验证的用例,怎么设计正常流、异常流和边界流,怎么保证测试环境不坑人,以及我踩过的那些“测了跟没测一样”的坑。不管你是刚入行的测试新人、身兼数职的开发,还是被迫兼职测试的产品经理,这套思路应该都能直接用上。
1. 测试这件事,到底在测什么
1.1 测试不是找bug,而是建立信心
很多人对测试的第一反应是“找bug”,这话对了一半。找bug只是手段,测试真正的目的是建立信心——让团队有底气说“这个功能在真实场景下能正常工作”。我在“ceshi”项目里最深刻的一个体会是:如果测试只是为了证明“有bug”,那测完只会收获一堆问题清单;但如果你带着“用户这么操作会不会挂”的视角去测,收获的会是一张可以放心上线的通行证。
这两种心态差别很大。前者容易让人陷入“为了提bug而提bug”的怪圈,测出来的问题又多又碎,开发看了只想摔键盘;后者则要求你先理解业务、理解用户、理解系统边界,然后设计出真正有杀伤力的用例。同样是测一个登录功能,前者可能只是“输入正确账号密码能不能登录”,后者会追问“密码错误五次会不会锁定”“验证码过期了再提交会怎样”“跨设备登录要不要踢掉旧会话”。这些追问,才是测试价值的来源。
“ceshi”项目当时是一个后台管理系统,功能不算复杂,但权限模型很绕。我一开始也犯过傻,拿着功能列表一个个点,测了半天自我感觉良好,结果一冒烟测试就翻车——有个角色的菜单权限配错了,我压根没覆盖到那条路径。从那之后我意识到,测试的核心不是“点得够不够多”,而是“有没有系统性地想清楚测什么、为什么这么测”。
1.2 五个测试层级:功能、边界、异常、性能、回归
我习惯把测试拆成五个层级,每个层级解决一类问题。这个框架不是教科书里抄来的,是我在项目里反复被坑之后总结出来的,分别对应“功能对不对”“边界稳不稳”“异常顶不顶”“扛不扛得住”“改了会不会坏”五个问题。
| 测试层级 | 核心问题 | 典型场景 | 我的建议 |
|---|---|---|---|
| 功能测试 | 功能对不对 | 登录、增删改查、流程流转 | 先保证主流程跑通,再补分支 |
| 边界测试 | 边界稳不稳 | 最大字符数、最大条数、空值、超长值 | 最容易暴露隐藏缺陷,别偷懒 |
| 异常测试 | 异常顶不顶 | 断网、超时、重复提交、权限不足 | 模拟用户乱操作和系统级故障 |
| 性能测试 | 扛不扛得住 | 并发登录、大列表加载、大量数据导出 | 不一定要压测平台,脚本也能跑 |
| 回归测试 | 改了会不会坏 | 新功能上线后检查老功能 | 优先级高的核心用例固定回归 |
这五个层级不是每次都要做全套。小改动、小迭代,功能加回归就够了;涉及到核心链路、大版本升级,五个层级都得过一遍。“ceshi”项目每次发版前我都有个固定动作:把五层测试的checklist拉出来,逐项确认哪些做、哪些不做、为什么不做的理由是什么。这个动作看着笨,但能避免很多“我以为不用测”的事故。
1.3 为什么很多项目“测了跟没测一样”
说实话,我在“ceshi”项目里最常听到的一句话就是“测了呀,但没发现这个问题”。测了跟没测一样,原因通常逃不出这三个。
第一个是用例设计太粗糙,只会照着需求文档写正向步骤,文档说“输入用户名密码点击登录”,用例就只写这一条,完全没考虑用户不会按你的剧本走。第二个是环境不一致,开发在自己电脑上测的是本地库,测试在测试环境测的是另一套数据,两边结果对不上,出了问题谁也说不清。第三个是回归不充分,新功能测得很仔细,老功能直接跳过,结果新代码一改,旧接口悄悄挂了。
这三个问题的根源都指向同一个词:系统性。“ceshi”项目后期,我把用例管理、环境规范、回归清单都固化成了文档和模板,从那之后“测了跟没测一样”的情况明显少了很多。测试最怕的不是测出bug,而是稀里糊涂地测完,完全不知道哪里没测。
2. 动手之前,先把测试用例设计清楚
2.1 需求拆解:从功能描述里挖出隐藏条件
测试用例的质量,七成取决于需求拆解做没做透。一个功能描述往往藏着一堆隐藏条件,这些条件才是测试的重点。“ceshi”项目里有个“角色权限分配”的功能,需求文档就写了一句“管理员可为用户分配角色”,听起来很简单对吧?但拆解之后能挖出至少八条隐藏规则:管理员能不能给自己分配角色?角色被分配后,已登录用户要不要立即生效?同一个用户能不能同时配多个角色?被删掉的角色挂在用户身上怎么办?权限变更后,用户已经打开的页面要不要强制刷新?
拆解需求的方法是逐词审查。拿到一句话,把里面的名词、动词、限定词一个个抠出来问:“管理员”是谁,普通用户行不行?“分配”是一次性操作还是可反复修改?“角色”有哪些状态,启用、禁用、删除分别怎么处理?
我习惯做一张需求拆解表,左边是原始描述,中间是拆出的隐藏条件,右边是每条条件对应的用例编号。这张表既是测试设计的依据,也是和开发、产品对齐需求理解的工具。很多时候我拿着拆解表去找产品确认,对方会愣一下说“这个场景我没想过”,然后我们就一起补进需求里——这种协作比单纯提bug有价值得多。
2.2 用例设计三件套:正常流、异常流、边界流
用例设计我从来不照着模板硬写,因为模板容易把脑子填满,却不告诉你哪些场景值得测。我用的是一个很朴素的三件套思路:正常流、异常流、边界流。正常流保证功能能跑通;异常流保证功能在用户乱操作时不会出洋相;边界流保证功能在极限输入下不会崩。
打个比方,测一个“上传Excel文件”的功能。正常流是“选一个格式正确的Excel,点上传,提示成功”;异常流包括“上传一个jpg图片”“上传一个空文件”“上传超过10MB的文件”“网络断开时点击上传”;边界流则是“文件名含特殊字符”“文件恰好10MB整”“工作表名字超长”“一万行数据的Excel”。
我见过很多测试只写正常流,异常流靠临场发挥,边界流直接放弃,这是很危险的。异常流和边界流恰恰是线上事故的高发区——用户不会照着操作手册用你的产品,他们会上传奇怪的文件、输入超长的文字、连点十次提交按钮。三件套里,后两件才是区分“会用测试”和“不会用测试”的分水岭。
2.3 一个实际用例表的写法示例
理论讲再多,不如直接看一张用例表。下面是我在“ceshi”项目里写过的“用户登录”用例表,字段不多,但每条用例都能直接执行、直接追溯需求,格式上可以做参考。
| 用例编号 | 用例名称 | 前置条件 | 测试步骤 | 预期结果 | 优先级 |
|---|---|---|---|---|---|
| LOGIN_001 | 正确凭证登录成功 | 测试账号已创建 | 输入正确账号密码,点击登录 | 登录成功,跳转首页 | 高 |
| LOGIN_002 | 密码错误提示明确 | 无 | 输入正确账号、错误密码,点击登录 | 提示“用户名或密码错误”,不跳转 | 高 |
| LOGIN_003 | 账号不存在提示明确 | 无 | 输入未注册账号、任意密码 | 提示“用户名或密码错误”,不暴露账号是否注册 | 高 |
| LOGIN_004 | 密码连续错误5次锁定 | 无 | 连续输入错误密码5次 | 第5次后账号锁定,提示联系管理员 | 高 |
| LOGIN_005 | 锁定账号无法登录 | 已通过LOGIN_004锁定账号 | 输入正确账号密码 | 提示“账号已锁定”,拒绝登录 | 高 |
| LOGIN_006 | 空值校验 | 无 | 账号或密码为空,点击登录 | 按钮置灰或提示“请输入账号/密码” | 中 |
| LOGIN_007 | 超长账号输入处理 | 无 | 输入超过50位的账号 | 输入框限制长度,或提交后明确报错 | 中 |
| LOGIN_008 | 登录接口并发重复请求 | 无 | 使用脚本并发发送10次登录请求 | 仅第一次成功,其余返回重复提交或排队处理 | 低 |
这个例子里,LOGIN_004、LOGIN_005属于典型的状态流转测试,因为锁定状态会影响后续登录行为;LOGIN_007是边界流;LOGIN_008是异常流里偏性能的用例。写用例表的关键不是追求数量,而是每条都有独立价值,写完自己问一句:“这条用例测挂了,改动代码的人能立刻明白是哪里出问题吗?”如果答案是否定的,说明用例写得还不够清楚。
3. 一次完整测试实操记录
3.1 准备测试环境:环境一致性怎么保证
很多人觉得测试环境不就是“装个软件连个库”嘛,错了。环境不一致是“测了跟没测一样”的头号元凶。“ceshi”项目早期就栽过:开发在本地库测得好好的,测试环境一跑就报错,折腾半天发现测试环境的数据库表结构和代码版本对不上,白费了一下午。
我后来立了三条规矩,照着做基本不会再被环境坑到。第一条,测试环境必须和代码版本严格对应,每次部署前核对代码分支、构建号、数据库版本号,记录在部署文档里,防止“代码是最新的、库是老旧的”这种错位。第二条,测试数据要可复用、可重置,准备一套标准的测试账号和数据,每个轮回开始前把库恢复到初始状态,确保用例可以重复执行。第三条,环境配置要文档化,涉及的环境变量、外部服务地址、端口号、mock开关都落到文档里,换个人也能把环境搭起来。
保持环境干净的另一个细节是数据隔离。不要让测试数据和真实数据混在一起,尤其是有定时任务、报表统计的功能,测试数据一旦混进去,跑出来的报表就会很奇怪,而且很难查清楚。我在“ceshi”项目里专门用一个标识字段标记测试数据,所有查询、统计类功能测试都用这套带标识的数据,测完一键清理。
3.2 执行测试:抓bug要记录哪些信息
进入执行阶段,最大的考验不是“发现了bug”,而是“把bug描述清楚”。说了你都不信,见过太多“点了一下就报错了,你们自己看看”这种bug描述,开发收到这种反馈连环境都复现不了,只能干瞪眼。我在“ceshi”项目里要求每条bug至少包含六个信息:操作步骤、实际结果、预期结果、环境信息、数据信息、复现概率。缺一个,开发就有理由把bug打回来。
操作步骤要一步步写清楚,最好精确到“点了哪个按钮、输入了什么值、停留了几秒”,不要省略“中间等了一会儿”这种看似无关的动作;实际结果要写现象,报错截图、接口返回、页面表现都行;预期结果写“本来应该怎么样”;环境信息写浏览器版本、系统版本、代码版本;数据信息写当时用的账号和测试数据;复现概率写“必现”还是“偶现”,偶现的还要记录当时的大概状态,比如是内存占用高的时候还是首次加载的时候。
这里插一句,责任心比技巧更重要。发现bug之后,我一般会先自己复现一遍,再想办法缩小触发条件。有一次我为了定位一个偶现的白屏问题,反复刷新了几十次,最后发现是每次都先快速切换两次菜单再进入某个页面才触发。这种复现路径写在bug单里,开发修起来效率极高,对我的信任也一下子建立起来了。
3.3 回归测试:改一处可能坏一片
回归测试是测试流程里最容易被压缩、也最不该被压缩的环节。项目越到后期,功能之间的耦合越深,“改一处坏一片”不是段子,是每天都会发生的事。“ceshi”项目里有一次,开发只是改了“用户列表”的查询SQL,结果“导出用户数据”功能跟着报错,因为导出模块复用了同一段查询逻辑。
我的做法是维护一张核心回归用例清单,这张清单不追求全,但覆盖所有重要的主流程和核心链路,数量控制在几十条以内,确保每次发版前能在半小时内手工跑完。清单用优先级标记用例,P0级是“不做就出事”的用例,比如登录、支付、数据保存;P1级是“出错会造成较大影响”的用例,比如报表查询、权限变更;P0和P1优先保证,P2视时间安排。
回归测试还有个容易被忽略的重点:不仅仅是点一遍就完事。“ceshi”项目里我会把本次发版涉及改动模块的关联用例单独加强执行,比如改动权限模块,就用例把“不同角色登录后看到的菜单”“同一个操作不同角色的权限表现”这种关联场景重新过一遍。光靠手动点击做回归很累,一旦出现频繁发版,建议尽快把P0用例自动化,虽然前期投入大,但后面每次发版节省的时间远超出投入。
4. 常见问题与排查技巧实录
4.1 “怎么我测不出来bug”的四个原因
经常有新人问我:“我按用例测了,为什么还是没发现问题?”这个问题我太有共鸣了,因为我也是在“测不出bug”这件事上栽过跟头的人。后来我总结出四个最常见的原因,每一条都对应一种思维转变。
第一个是只测happy path,照着需求文档一步步走,所有操作都按照产品预设的正常路径来,这样测出来的当然是好的系统。第二个是边界值用得太保守,比如输入框限制50个字,你填了49个觉得差不多了,但其实真正该测的是50个、51个、空值、纯空格、超长中文字符。第三个是状态变化没测透,很多bug出在“先后顺序”上,比如先做了A操作再做B操作和先做B再做A,结果完全不同,只测一种顺序就发现不了。第四个是缺乏怀疑精神,看到页面显示成功就以为真的成功了,没有去数据库确认数据到底有没有写进去,也没有验证返回的接口是不是真的符合预期。
我记得有一回测“批量删除”功能,界面上确实提示“删除成功”,但一刷新数据又回来了——问题出在后端根本没执行删除,只返回了一个成功标志。如果只看页面,这就是一条怎么测都测不出来的bug。从那以后我多了个习惯:页面验证永远只是第一层,数据层面、接口层面的验证才能给出确凿答案。
4.2 测试环境与生产环境不一致怎么办
环境不一致,是测试领域最经典的老大难问题。我遇到过的情况包括:测试环境的数据库里少了一张新表,导致功能直接白屏;生产环境用的是CDN缓存,测试环境没有,导致某些静态资源加载效果完全不同;还有连接的外部服务在测试环境是mock的,生产环境却是真实的,mock时一切正常,一上线就暴露出超时和异常处理问题。
碰到这种问题,我的第一原则是“尽早承认环境差异,并在测试计划里写明哪些差异是已知的、接受的风险是什么”。你不可能把测试环境做到和生产100%一致,但你可以尽量逼近,同时明确剩下的差异。比如数据库表结构严格保持一致,外部服务除了下游权限受限的,其余都尽量走真实调用,只在必须mock的地方mock,并且把mock行为和真实行为的潜在差异标识出来。
另外推荐一个低成本高收益的办法:做一次上线前的生产环境冒烟测试。在流量比较低的时候,挑核心链路在生产环境跑一遍关键用例,时间控制在十几分钟以内,既不会影响真实用户,又能提前发现“测试环境一切正常、生产环境一跑就挂”的问题。“ceshi”项目上线前,团队靠这个动作拦下过两个很隐蔽的配置问题,价值远大于花在冒烟测试上的那点时间。
4.3 用例遗漏的补救方法
无论用例设计得多完善,总有漏掉的情况。漏掉本身不可怕,可怕的是漏掉之后不知道怎么补救、也不知道怎么防止再漏。“ceshi”项目里我常用的补救方法有三个,都挺实用。
第一个是跟着bug清单反查用例。每个线上反馈的bug、每个测试中发现的漏网bug,都回头看看用例集里是否缺少对应的用例,缺了就补进去,形成“发现一个、补一个、固化一个”的闭环。第二个是请旁观者来做一次测试。我会邀请不太熟悉这个模块的同事拿着需求文档自己走一遍,因为旁观者没有我脑子里那些“这个功能应该是这样”的先入为主,更容易发现我从没想过要测的场景。第三个是复盘线上事故。每一次线上事故都是最珍贵的用例素材来源,把这些场景写进回归清单,下次就不会再犯。
我还会周期性对整个测试集做“冗余审查”:有些用例写完之后可能随着需求变更已经失去意义,或者和其他用例重复了,这种情况留着既浪费执行时间又干扰重点,及时删掉才能保持用例集的高信噪比。维护用例集和程序员重构代码很像——不断删除无效表达,留下来的才都是精华。
4.4 问题速查表:常用排查思路
最后把我常用的排查思路做成一个速查表,遇到问题可以先按图索骥,不用每次都从头开始折腾。
| 现象 | 优先排查方向 | 具体动作 | 我的经验备注 |
|---|---|---|---|
| 功能报错或白屏 | 前端控制台报错 | 打开浏览器开发者工具,看Console和Network | 先分清前端报错还是后端接口报错 |
| 接口返回异常 | 后端日志 | 查看请求参数、返回体、服务端日志堆栈 | 带token或身份的请求先确认凭据是否有效 |
| 数据不对 | 数据库核对 | 用同一个操作对应的SQL查询库里数据 | 加筛选条件,防止测试数据混入报表 |
| 偶现问题 | 复现路径 | 记录操作顺序、间隔时间、当时数据量 | 多复现几次,缩小触发条件范围 |
| 部署后新增功能异常 | 版本一致性 | 核对代码分支、构建号、数据库迁移脚本 | 最常见的是库没跑迁移脚本 |
| 慢或者超时 | 性能分析 | 观察接口耗时、数据库慢查询、外部调用时长 | 先确定瓶颈在外部接口还是内部逻辑 |
这个表不是万能的,但它能在你脑子里一片空白的时候给你递一个抓手。遇到问题先按表排查,排查不出来再说——大部分问题到不了“排查不出来”那一步,多的是在第二步就已经定位了。
做测试这些年,我最深的体会是:测试不是一个把功能“点一遍”的任务,而是一整套系统性的思考方式。从需求拆解到用例设计,从环境准备到bug描述,每个环节都有它的方法论和坑。回到“ceshi”这个随手命名的项目,它让我明白了一件事——给项目起什么名字不重要,重要的是你有没有真的把它当成一个需要认真对待的测试过程。你把测试当回事,它就会在你上线的那一刻回报你;你不把测试当回事,它就会在用户的手上报复你。