一场测试岗技术面进行到二十多分钟【推断】,面试官从桌上抽出一张白纸推过来:「给一个账号密码登录功能写测试用例,5 分钟【推断】。」
大多数人的第一反应是立刻动笔:用户名正确密码正确、用户名错误、密码错误、用户名为空、密码为空、用户名带空格、密码含特殊字符……写到第 12 条【推断】,笔停了,时间也到了。面试官抬头问一句:「还有吗?」你脑子里其实还飘着一堆模糊的东西,验证码、失败锁定、手机端、老版本 App,但一个都说不出口,因为它们没有挂在任何结构上。
这道题几乎每场测试岗技术面都会遇到,也几乎每场都能拉开差距。差距不在你知道多少条,而在你有没有一套稳定的展开顺序,以及你敢不敢在动笔之前先开口提问。下面这份脚手架分三段:先问什么、按什么顺序铺开、最后怎么收口。
一、这道题考的不是格式,是你的展开顺序
先说结论:面试官递纸的那一刻,他心里已经有一份自己的答案了,他不是来数条数的。他在看三件事。你会不会先问清边界,这说明你知道需求不明就没法设计用例;你的展开有没有稳定层次,这说明你换一个业务也能自己铺开,而不是背模板;你能不能说清优先级,这说明你知道测试资源永远不够用。条数只是这三件事的副产品。
一张写得密密麻麻、12 条【推断】全在功能层打转的纸,传递的信号是「他只顺着输入框想」;一张只有 8 条【推断】、却把安全与并发都点到的纸,传递的信号是「他知道线上事故一般出在哪儿」。格式从来不是评分项,你写字的顺序里有没有可复用的骨架,才是。
二、开口先问的 4 个问题
动笔前先开口,这是最容易被忽略、也最容易加分的一步。别问「这个功能怎么实现的」,要问那些会直接改变用例形态的问题。
有没有验证码?是图形码、短信码还是滑块?这决定异常层要不要区分「码错」和「码过期」。
支不支持第三方登录或免密登录?这决定功能层要不要多一组账号体系入口。
连续失败几次锁定、锁多久、锁账号还是锁 IP?这是安全层最贵的一条,问清楚了才有资格写。
密码策略是什么、要不要兼容老版本 App 与老浏览器?这决定兼容与体验层的边界在哪。
问完这 4 个问题,你手上就有了别人没有的信息,纸上的用例天然深一层。就算面试官说「你按最普通的情况假设」,你也已经证明了自己知道该在哪儿追问。这一步只花 1 分钟【推断】,却是后面所有层次的入口。
三、五层展开:功能、异常、安全、兼容与体验、性能与并发
拿到边界之后,按固定顺序铺开,每层写 2 至 3 条【推断】代表性用例,不追求穷举。
功能层:正向通路。正确账号密码能登录、登录后拿到会话、退出后会话失效。这层写最少的条数,因为所有人都能想到。
异常层:错误密码、用户名不存在、用户名或密码为空、超长输入、前后空格、大小写是否敏感、错误提示是否统一(提示不能暴露「用户不存在」这类信息)。
安全层:失败次数锁定、锁定后即使密码正确也拒绝、密码是否明文传输与明文存储、验证码能否被绕过、同一账号多地登录的会话互踢、输入框里的注入是否被拦。
兼容与体验层:主流浏览器与老版本 App、弱网与超时提示、输入法自动填充、密码可见切换、读屏能否读到错误提示。
性能与并发层:登录接口的响应与吞吐、早高峰大批用户同时登录、暴力破解时的限流、后端被打满时的降级表现。
顺序本身就是信号:先窄后宽,先必然后风险。写到第三层,面试官通常已经开始追问了,这正是你想要的,追问意味着他在跟你讨论,而不是在给你打分。
四、5 分钟怎么分:1 + 3 + 1【推断】
时间一紧,人就会退回罗列。所以时间要提前分好,而不是写到一半才发现只剩 30 秒【推断】。
1 分钟【推断】提问:4 个问题一次说完,边听边在纸角记关键词。
3 分钟【推断】展开:五层各写 2 至 3 条【推断】,只写层次、用例名和预期结果。
1 分钟【推断】收口:讲优先级、讲需要哪些测试数据与环境、讲哪些点因为时间关系没铺开。
诀窍是「不写步骤」。手写用例不是交付文档,纸面上只要层次加用例名加预期,步骤留给口述。把写步骤的时间省下来,你能多展开一整层。
五、收口话术:用优先级把答案钉住
最后 1 分钟【推断】决定面试官记住你什么。别用「我写得差不多了」收口,换成这三句。
「如果时间只够测三类,我优先覆盖失败锁定、密码传输与存储、登录接口限流,因为线上事故里它们最贵:前两类关系到用户数据,后一类关系到整个入口还能不能用。」
「这些用例要跑起来,我需要三样东西:一批可复位的测试账号、一个能重置锁定状态的后台开关、一套能模拟弱网与并发的环境。」
「时间关系我没有展开兼容层的细节,如果这是要发布的版本,我会再补一轮老版本 App 与主流浏览器的回归。」
第一句给优先级,第二句给工程意识(你要资源、你要可复现),第三句主动交代遗漏风险。第三句尤其值钱,它证明你清楚自己答案的边界在哪儿,而不是在硬撑。
六、两种答案,在面试官眼里差多少
把这份脚手架和最常见的罗列式答案摆在一起看,差别一眼可见(下表中的条数与分钟数均为本文示例设定值【推断】):
维度
罗列式答案
分层式答案
面试官关注点
你能想起多少条
你有没有稳定的展开顺序
能拿到的分
基础分:知道要测什么
结构分:知道按什么顺序测、先测哪个
被追问的深度
一句「还有吗」就卡住
追问落在某一层里,能顺着往下讲
5 分钟内可展开的条数【推断】
10 至 12 条,全挤在功能与异常
8 至 10 条,但五层都点到了
后续追问应对
只能补零散的新条目
能把新问题挂回已有层次,顺势讲优先级
结论很直白:罗列式答案的天花板是「记得多」,分层式答案的天花板是「带得动新业务」。团队要招的是能自己铺开测试范围的人,不是能背用例清单的人。
七、加分动作:把口述的用例表落成可跑代码
如果岗位偏测试开发,还有一个能把分拉开的动作:口述完用例表,补一句「这张表我平时会直接落成参数化用例,表里一行就是一条用例」,然后把下面这段代码的思路讲给他听。
文件:test_login_cases.py
运行:python test_login_cases.py (或 pytest -v test_login_cases.py)
import pytest
MAX_FAIL_TIMES = 3 # 连续失败几次锁定:本文示例设定值
CAPTCHA_AFTER = 2 # 连续失败几次后要求验证码:本文示例设定值
LOCKED = "ACCOUNT_LOCKED"
NEED_CAPTCHA = "CAPTCHA_REQUIRED"
class FakeLoginService:
"""内存版登录服务,代替真实后端,保证这段代码能直接跑起来。"""
def __init__(self):
self.users = {"tester01": "Passw0rd!"}
self.fail_count = {}
self.locked = set()
def login(self, username, password, captcha=None):
if username in self.locked: # 锁定优先于一切校验
return LOCKED
if self.fail_count.get(username, 0) >= CAPTCHA_AFTER and captcha is None:
return NEED_CAPTCHA
if self.users.get(username) == password:
self.fail_count[username] = 0
return "OK"
self.fail_count[username] = self.fail_count.get(username, 0) + 1
if self.fail_count[username] >= MAX_FAIL_TIMES:
self.locked.add(username)
return "FAIL"
口述的那张用例表:一行一条,第一个字段就是面试时说出口的层次
CASE_TABLE = [
("功能", "正确账号密码登录成功", "tester01", "Passw0rd!", None, "OK"),
("异常", "密码错误登录失败", "tester01", "wrong-pass", None, "FAIL"),
("异常", "用户名不存在", "no_such_user", "Passw0rd!", None, "FAIL"),
("异常", "用户名为空", "", "Passw0rd!", None, "FAIL"),
("安全", "连续失败 2 次后要求验证码", "tester01", "Passw0rd!", None, NEED_CAPTCHA),
("安全", "连续失败 3 次触发锁定", "tester01", "wrong-pass", None, LOCKED),
("安全", "锁定后密码正确也拒绝", "tester01", "Passw0rd!", None, LOCKED),
]
前置条件:这条用例开跑之前,账号已经失败过几次
PRE_FAILS = {
"连续失败 2 次后要求验证码": CAPTCHA_AFTER,
"连续失败 3 次触发锁定": MAX_FAIL_TIMES,
"锁定后密码正确也拒绝": MAX_FAIL_TIMES,
}
@pytest.fixture
def svc():
# 每条用例都拿一个干净实例,避免上一条把账号锁死、污染下一条
return FakeLoginService()
@pytest.mark.parametrize(
"layer,title,username,password,captcha,expected",
CASE_TABLE,
ids=[row[1] for row in CASE_TABLE],
)
def testlogin(svc, layer, title, username, password, captcha, expected):
for in range(PRE_FAILS.get(title, 0)):
# 带码绕过验证码门槛,只累计失败次数,把前置条件打出来
svc.login(username, "wrong-pass", captcha="1234")
assert svc.login(username, password, captcha) == expected
def test_case_table_covers_all_layers():
# 用例表本身也要被测:至少覆盖功能、异常、安全三层
layers = {row[0] for row in CASE_TABLE}
assert {"功能", "异常", "安全"}.issubset(layers)
if name == "main":
raise SystemExit(pytest.main([file, "-v"]))
这段代码的关键不在语法,而在三件事。第一,用例表是一份数据(CASE_TABLE),不是一堆函数:你在纸上写的那张表可以原封不动搬进来,一行一条,加一条用例就是加一行,不用复制粘贴一个函数。第二,第一个字段 layer 就是你口述的层次,于是「这张表覆盖了哪几层」变成可以断言的事实,最后那条 test_case_table_covers_all_layers 测的不是登录功能,而是用例表本身有没有偏科。第三,锁定策略那条用例不是简单写一个期望值,而是先用 PRE_FAILS 把失败次数打到阈值、再验证锁定生效,这正好对应提问环节那句「连续失败几次锁定、锁多久」:你问清楚了前置条件,才有资格把它写成断言。pytest 的参数化会把这张表在报告里展开成一条条独立用例,ids 让用例名直接显示出来,失败时你一眼能看到是哪一层哪一条挂了。
八、把脚手架练成肌肉记忆
这份脚手架不用背,练三遍就行:挑一个你熟的业务,注册、下单、找回密码都可以,掐 5 分钟【推断】,先问自己那 4 个问题,再按五层铺开,最后用优先级收口。练到第三遍,动笔前那 1 分钟【推断】提问会变成本能,而这恰恰是面试官最想看到的东西。
手写用例这道题,从来不是在数你写了多少条。
面试官递过来的那张纸,考的不是记性,而是你脑子里有没有一套能带着走的展开顺序:先问清边界,再分层铺开,最后用优先级收口。
你被要求「手写登录用例」时,开口第一句是提问,还是直接开始罗列?评论区聊聊。