面试官问:"你们接口自动化里怎么处理 302 跳转?"我当时的回答是"关掉自动重定向",然后就没有下文了。对方接着追问:"那 302 和 307 在重定向时对请求体的处理有什么区别?你们的用例为什么选这个而不是那个?"那一刻我才意识到,应用层这块知识在测试开发面试里根本不是"背几个协议名"能糊过去的,它考的是你能不能把协议细节翻译成用例设计。
上一篇把应用层的基础协议过了一遍,HTTP、HTTPS、DNS、FTP、邮件协议的名词解释和端口列表都摆在那儿了。但真正坐在面试桌对面,或者在工位上排查一个"明明代码没问题但就是报错"的问题时,光知道端口号远远不够。这一篇补的就是这些东西:报文字段级的细节、握手链路上的异常点、非 HTTP 协议在测试场景里的特殊表现,以及怎么把协议知识落成抓包、Mock、断言这三样可交付的产物。不管你是刚开始准备测开面试,还是已经做了两三年接口测试但总觉得"知其然不知其所以然",下面的内容应该都能对上号。
1. 应用层在测开面试里的真实权重
1.1 面试官追问应用层时,究竟在筛什么
传输层和网络层的知识,面试官问起来大多停在"TCP 三次握手为什么不是两次""TIME_WAIT 太多怎么处理"这类有标准答案的题目上,答对答错都很快能判断。应用层不一样,它离业务最近,同一道题往下挖三层,候选人的真实水平就藏不住了。
第一层是名词层:HTTP 默认端口、HTTPS 用的是什么、DNS 走 UDP 还是 TCP。这一层筛掉的是完全没准备的人,答不上来基本就结束了。
第二层是报文层:一个请求发出去,请求行长什么样,首部字段有哪些,消息体用什么编码。这一层筛掉的是"只会用工具点一点"的人。很多人做了一年接口测试,从来没打开过原始报文看过,Postman 里勾勾选选就把请求发出去了,一旦遇到服务端说"你的 Content-Type 不对",就只会反复试。
第三层是验证层:如果某个字段改了会怎样,怎么构造这个场景,出了问题怎么定位到是应用层还是下面几层。这一层才真正区分"会测"和"能设计测试"。比如问 DNS,第一层问端口 53,第二层问 A 记录和 CNAME 的区别,第三层就会问:"线上偶发首包耗时 3 秒以上,你怎么判断是不是域名解析的问题?"这个问题没有标准答案,但答得好不好,一听就知道。
我在准备复习的时候给自己定了个判断标准:任何一个应用层协议,如果我说不出"它在什么情况下会慢、会错、会被中间设备改掉",那这个协议我就没算学会。这套标准后来在面试里救了我好几次。
1.2 一份自测清单:应用层该复习到什么颗粒度
光说"要深入"太虚,下面这张表是我自己复习时用的颗粒度清单,左列是协议,中间是必须能脱口而出的点,右列是面试里最容易被追问到底的方向。你可以拿它当检查表,哪一行卡壳了就回去补哪一行。
| 协议 | 必须掌握 | 高频追问方向 |
|---|---|---|
| HTTP/1.1 | 报文三段结构、方法语义、状态码分类、连接复用 | 幂等性怎么测、304 是怎么产生的、Connection 头的作用 |
| HTTP/2、HTTP/3 | 多路复用、头部压缩、QUIC 基于 UDP | 多路复用下怎么定位单个流的慢请求 |
| HTTPS/TLS | 握手轮次、证书链、SNI、ALPN | 自签证书为什么在测试环境报错、抓不到明文怎么排查 |
| DNS | 记录类型、递归与迭代、TTL | 灰度切换后流量还打旧机器、首包慢怎么归因 |
| DHCP | 四步交互、租约、地址池 | 测试机自己改了静态 IP 导致冲突 |
| 邮件协议 | SMTP 发、POP3 收、IMAP 同步 | 附件编码、中文主题乱码 |
| FTP | 主动模式与被动模式的端口差异 | NAT 环境下传不上去 |
| WebSocket | Upgrade 握手、帧、心跳、关闭码 | 断线重连怎么测、鉴权放在哪一步 |
| gRPC | HTTP/2 承载、Protobuf 序列化、四种流式 | 状态码在 trailer 里怎么断言 |
| MQTT | 发布订阅、QoS 三档、遗嘱与保留消息 | QoS1 重复投递怎么处理 |
| NTP | 层级、偏移、时钟回拨 | 时间不同步导致的签名校验失败 |
这张表不是让你背,是让你知道自己缺哪块。我当时填这张表,发现自己对 FTP 和 SNMP 几乎是空白,对 gRPC 只停留在"听说过"的程度,后来专门补了两天。补的时候不是为了面试,是因为那段时间正好在做一个长连接推送的测试,不补根本推进不下去。
2. HTTP:报文拆到字段级,用例才写得出来
2.1 一行请求行里藏着三个测试点
请求行就三部分:方法、请求目标、协议版本,比如POST /api/v1/orders?from=web HTTP/1.1。看起来很朴素,但我见过太多用例就死在三个不起眼的地方。
第一是方法语义和幂等性。GET、HEAD、PUT、DELETE 在语义上是幂等的,POST 不是。这个知识点的价值不在于背定义,而在于它能直接指导用例设计:对幂等方法,重复调用之后的资源状态应该和调用一次一致,这就可以写一条"重复提交校验"的用例;对非幂等方法,重复调用会创建多条记录,那就得写"重复提交防护"的用例,比如前端按钮置灰、后端 token 去重、数据库唯一索引。面试里被问到"你怎么测接口幂等",如果只答"多调几次看结果一样不一样",那就太浅了,得说清楚幂等键放在哪、重复请求返回什么、状态码是 200 还是 409。
第二是请求目标里的查询串。?后面的部分在服务端解析时很容易出问题:参数重复出现时取第一个还是最后一个、空值传a=和完全不传a是不是一个意思、特殊字符有没有做百分号编码、+号在查询串里到底代表空格还是加号。这几个点在测试里全是现成的用例。我自己就踩过一次,一个搜索接口在页面里搜"C++",传过去变成了"C ",因为+被按查询串规则解码成了空格,最后是靠百分号编码%2B才解决。
第三是协议版本。HTTP/1.0 默认短连接,HTTP/1.1 默认长连接,这个差别会直接影响压测结果。同一套脚本,如果服务端对 HTTP/1.0 的请求每次都关连接,QPS 会掉一大截,而你在压测报告里看到的是"响应时间变长了",很容易误判成服务端性能问题。
2.2 状态码按"责任方"分类记忆
背状态码列表是效率最低的方式,我自己的做法是按"出问题时先查哪一侧"来分类。这样记的好处是,线上看到一个状态码,脑子里第一反应不是"这是什么意思",而是"我该去哪儿看日志"。
| 状态码 | 语义 | 先查哪一侧 | 测试里的典型场景 |
|---|---|---|---|
| 400 | 请求语法或参数不合法 | 用例数据 | 字段类型传错、JSON 格式坏掉 |
| 401 | 未认证或凭证失效 | 鉴权链路 | token 过期、签名时间戳超窗 |
| 403 | 已认证但无权限 | 权限配置 | 越权访问、角色未绑定 |
| 404 | 资源不存在 | 路由与 ID | 路径拼错、软删数据仍被查 |
| 405 | 方法不被允许 | 用例方法 | 本该 POST 写成 GET |
| 409 | 状态冲突 | 并发与重复提交 | 库存扣减并发、重复下单 |
| 413 | 请求体过大 | 客户端与网关限制 | 附件上传边界值 |
| 429 | 触发限流 | 脚本并发配置 | 压测时没配限流白名单 |
| 500 | 服务端未捕获异常 | 服务日志堆栈 | 空指针、类型转换失败 |
| 502 | 网关拿到无效上游响应 | 上游存活与端口 | 服务刚重启、端口未监听 |
| 503 | 服务暂不可用 | 熔断与线程池 | 依赖降级、连接池打满 |
| 504 | 网关等待上游超时 | 上游耗时 | 慢查询、下游级联超时 |
这里有个特别值得说的点:502 和 504 经常被混为一谈,但排查路径完全不同。502 是网关连上了上游但拿到的响应不合法,或者根本连不上,属于"上游活着吗"的问题;504 是网关等太久了主动放弃,属于"上游太慢"的问题。线上看到 502,先 ping 端口、看进程;看到 504,先看慢日志和链路耗时。这个区分在面试里说出来,对方基本能判断你是真排查过。
还有一个容易被忽略的:304。它不是错误,是协商缓存命中。304 的响应里没有消息体,如果自动化用例的断言里写了"响应体不为空",这条用例就会莫名其妙挂掉。我见过一个团队为了解决这个问题,把所有 GET 请求都加了个随机参数来绕过缓存,结果把缓存本身的正确性彻底测不到了,这是典型的用错误方法掩盖问题。
2.3 首部字段的冲突组合最容易出事故
单个首部字段的含义查文档就有,真正出事故的是组合。我把遇到过的几类整理一下,都是可以直接变成用例的。
Content-Length和Transfer-Encoding: chunked同时出现,是最经典的一类。这两个首部都在描述消息体边界,但描述方式不同。不同的网关、不同的服务端框架在处理这种报文时可能选择不同的判定依据,结果就是请求体被切错位置,后面的内容被当成新的请求解析。测试的做法是专门构造这种报文打给网关,观察它接受还是拒绝、拒绝时返回什么状态码。合规的网关应该直接返回 400,如果它默默接受了,那就说明解析策略存在歧义,这在联调环境里是必须记录的风险项。
Host缺失或被改写,是第二类。HTTP/1.1 强制要求带 Host,缺了就是 400。但在多租户或者多域名共用一套后端的环境里,Host 决定了路由到哪个虚拟主机。测试环境的域名和后端的 server_name 不一致时,经常会出现"请求发出去了,返回的是默认站点的页面",看起来像 404,其实是 Host 匹配失败。
Connection和中间设备的空闲超时,是第三类,也是最难查的。客户端以为长连接一直有效,但中间的负载均衡或者网关配了 60 秒空闲就断,第 61 秒客户端还往这条连接上写数据,就会拿到一个 RST 或者干脆卡住。这类问题的表现是"偶发超时,重试就好了",如果不抓包几乎定位不到。我在一个项目里遇到过,花了整整一天才确认是网关的空闲超时比客户端的连接池保活时间短,最后把客户端保活间隔调到 30 秒才稳定下来。
Cookie上的SameSite、Secure、HttpOnly三个属性也值得单独过一遍。HttpOnly意味着前端脚本读不到,如果你的自动化用例是靠前端脚本取 cookie 传给接口的,那遇到HttpOnly就直接断了,得改成从响应头的Set-Cookie里解析。SameSite=Lax则会影响跨站请求带不带 cookie,跨域场景下的登录态测试必须考虑这一条。
3. HTTPS 与证书:测试环境里最经典的"玄学报错"
3.1 握手过程压缩成三句话
TLS 握手细节能讲半小时,但面试和工作里真正用得上的,其实可以压缩成三句话。
第一句:客户端先发 ClientHello,里面带上自己支持的协议版本、加密套件列表、一个随机数,以及 SNI——也就是它想访问的域名。SNI 这一项特别关键,因为一个 IP 上可能挂了很多个域名,服务端靠它决定返回哪张证书。抓包里如果看到 ClientHello 里的 SNI 是空的,而服务端又确实配了多张证书,那基本就能确定问题出在客户端没带 SNI。
第二句:服务端回 ServerHello 加上证书链,双方通过密钥交换算法协商出会话密钥。TLS 1.2 需要两个往返,TLS 1.3 压缩到一个往返,而且把密钥交换也塞进了 Hello 消息里。这个差别的实际意义是,TLS 1.3 的首包耗时明显更低,如果你的接口测试关注首包时间,那 TLS 版本就是一个必须固定的变量。
第三句:握手完成后双方各自发 Finished 校验,之后才是加密的应用数据。所以你在抓包里看到的第一个 HTTP 请求,永远在 TLS 握手之后。
还有一个细节值得单独提:ALPN。客户端在 ClientHello 里会声明自己支持的应用层协议,比如 h2 和 http/1.1,服务端选一个回过去。如果客户端声明支持 h2 但服务端只认 http/1.1,协商会降级;如果两边声明不匹配又都不肯让步,握手直接失败。这类问题在做协议升级测试时很常见,表现是"浏览器能打开,命令行工具打不开",因为两者的 ALPN 声明不一样。
3.2 证书校验失败的五种成因和排查顺序
测试环境里最常见的一类报错就是证书相关,报错信息五花八门,但成因基本就那么几种。我按排查成本从低到高排个序,照着走一遍基本能定位。
第一种,系统时间不对。证书有生效时间和过期时间,服务器或客户端的时钟偏出去几个月,证书就直接判定无效。这个排查成本最低,一条date命令就能确认。虚拟机挂起之后恢复、容器基础镜像时间没同步,都会导致这个问题。
第二种,域名不匹配。证书的 SAN 字段里必须包含你实际访问的域名。这里有两个坑:一是通配符证书*.example.test只匹配一级子域,a.b.example.test就匹配不上;二是访问时用的是 IP 而不是域名,而证书里只有域名,那必然失败。
第三种,证书链不完整。服务端只配了叶子证书,没把中间证书一起发过来,客户端本地又没有缓存过这个中间证书,链就断了。判断方法很直接,用命令行工具连上去把证书链打出来看有几张,正常应该是叶子、中间、根(根有时可以不带,客户端本地有)。这类问题在用自建证书的内网环境里特别常见。
第四种,信任根缺失。自签证书或者内部 CA 签的证书,客户端默认不信任。解决办法要么把根证书导入信任库,要么在测试客户端上显式指定 CA 文件。生产环境千万别用"关闭校验"这一招,测试环境偶尔为了方便可以,但要在用例里明确标注这是个临时手段。
第五种,吊销状态检查阻塞。有些客户端会去查证书吊销状态,如果这个查询地址不可达,可能会一直等到超时,表现就是"连接特别慢最后失败"。这类问题最隐蔽,因为证书本身完全合法。
排查的时候我习惯用两条命令打头阵,先看服务端到底发了什么:
# 看握手过程和证书链 openssl s_client -connect api.example.test:443 \ -servername api.example.test -showcerts </dev/null # 看详细的请求响应头,确认协议版本和重定向 curl -vI https://api.example.test/health第一条命令里-servername参数千万别省,省了就不发 SNI,多域名环境下拿到的可能是默认证书,你会误判成"证书配错了"。
3.3 抓不到明文时的分层定位思路
HTTPS 抓包看不到内容,很多人的第一反应是"装个证书就行了"。但装证书这件事本身就会改变客户端的信任链,可能把真实问题掩盖掉。我的做法是先分层定位,确认问题到底在哪一层,再决定要不要解密。
先看 TCP 层:连接建起来了吗?有没有 RST?有没有大量重传?如果 TCP 层就不干净,那跟 TLS 一点关系都没有。
再看 TLS 层:握手完成了吗?ClientHello 发出去之后有没有 ServerHello 回来?如果没有,可能是端口不通、可能是服务端不支持客户端声明的版本、也可能是中间设备直接拦了。如果握手完成了但很快收到一个 alert,那就去看 alert 的类型,不同的 alert 对应不同的失败原因。
最后才看应用层:确实需要看明文,再考虑在测试客户端上配置解密。这时候要注意,解密只对你能控制的客户端有效,服务端之间的流量是解不了的。
这个分层思路在面试里答出来,比直接说"我用抓包工具"要有分量得多,因为它体现的是归因方法,而不是工具熟练度。
4. DNS、DHCP、NTP:三个不显眼但要命的基础协议
4.1 DNS 的递归与迭代,以及三个高频坑
DNS 查询分两种模式,这个概念面试里问得不少,但很多人答得含糊。简单说,递归是"你帮我查到底,给我最终答案",迭代是"你告诉我下一步该问谁,我自己去问"。客户端发给本地 DNS 服务器的通常是递归查询,本地 DNS 服务器去问根、问顶级域、问权威服务器这个过程是迭代的。
记录类型里最常用的几个:A 记录给 IPv4,AAAA 给 IPv6,CNAME 是别名,MX 是邮件服务器,TXT 常用来做域名验证,NS 是权威服务器,PTR 是反向解析。测试里最常打交道的是 A、CNAME 和 TXT。
坑一:本地 hosts 覆盖。开发或者测试为了图方便,在 hosts 文件里把域名指到某台机器上,过两天忘了删,结果所有请求都打到那台机器。表现是"代码明明改了但行为没变",查半天查不到。我现在的习惯是遇到"改了没生效"的问题,第一个动作就是看 hosts。
坑二:TTL 缓存导致切换延迟。做灰度或者机房切换的时候,把 DNS 记录改了,但因为各级缓存还留着旧记录,一部分流量仍然打到旧地址。这个不是故障,是设计使然。测试这类场景要明确 TTL 设了多久,并且要在切换后观察足够长的时间窗口,别一看还有旧流量就判定失败。
坑三:CNAME 链过长或者解析超时导致首包慢。CNAME 套 CNAME,每多一层就多一次查询,链太长的时候解析耗时会明显上升。这类问题的表现是"接口本身很快,但整体耗时高",如果只看服务端日志是看不出来的,必须在客户端侧计时,把 DNS 耗时和连接耗时分开统计。
验证解析链路最直接的办法是加 trace:
# 从根开始逐级追踪解析过程 dig api.example.test A +trace # 指定某个 DNS 服务器查询,对比不同服务器的返回 dig @203.0.113.53 api.example.test A +short第二条命令里的 IP 用的是文档专用地址段,实际用的时候换成你们环境里的 DNS 服务器地址就行。对比不同服务器的返回,能快速判断是权威记录配错了,还是某级缓存没刷新。
4.2 DHCP 四步交互与地址冲突
DHCP 的四步交互是 Discover、Offer、Request、Ack,这个顺序面试里问得很多。客户端广播找服务器,服务器回一个可分配的地址,客户端再广播确认要哪个,服务器最后确认。后面还有 Release 释放和 Nak 拒绝,但核心就是这四步。
这套机制在测试环境里的价值在于排查"机器上不了网"的问题。测试机拿不到地址,先看是不是地址池满了,再看是不是有人手工配了静态 IP 撞了。静态 IP 和 DHCP 分配的地址冲突是很常见的,因为管理员配静态地址的时候不会去查 DHCP 的租约表。有些 DHCP 服务器在分配前会做一次地址探测,如果发现这个地址有人在用就换一个,这个机制叫冲突检测,可以配,但很多环境没开。
还有一个场景是测试环境的机器频繁创建销毁。容器或者虚拟机批量起来的时候,短时间内大量 DHCP 请求,地址池容易被瞬间耗尽,表现是"部分机器起来后网络不通"。这类问题在压测准备阶段特别容易遇到,提前把地址池大小和租约时间算一遍能省很多事。
4.3 NTP 时间漂移引发的连锁失败
NTP 平时没人关注,但它出问题的时候,表现极其"玄学"。我遇到过一台上游服务的时间比标准时间慢了五分钟,结果所有经过它的签名校验全部失败,报错信息是"签名无效",查了整整一个下午才发现是时钟问题。
时间偏差会导致哪几类故障?第一类是带时间戳的签名和令牌,偏差超过允许窗口就判定过期或者未生效。第二类是定时任务,时钟回拨可能导致同一个任务被执行两次,或者干脆跳过。第三类是日志排查,多台机器时间不一致,你按时间线拼出来的调用链完全是错的。第四类是分布式锁和缓存过期,依赖时间的机制在时钟跳变时行为不可预测。
NTP 本身是有层级的,stratum 0 是基准时钟,往下逐级同步。层级越高,精度越差。测试环境里常见的问题是所有机器都指同一个内网时间源,如果这个源本身不准,那全环境一起错。我的做法是在自动化用例的公共前置里加一条时间偏差检查,超过阈值直接失败,比事后一个个查要省时间得多。
5. 非 HTTP 协议族:从邮件到消息推送的测试差异
5.1 邮件协议的分工与编码坑
SMTP 负责发,POP3 和 IMAP 负责收,这个分工很多人知道,但后面的差别就不一定说得清了。POP3 的典型行为是把邮件下载到本地并从服务器删除,适合单设备;IMAP 是双向同步,客户端的已读、删除、文件夹操作都会同步回服务器,适合多设备。
测试邮件相关功能时,真正麻烦的不是协议本身,是编码。邮件主题里的中文要用特定编码方式包裹,附件要用 base64 或者 quoted-printable,正文可能是纯文本、HTML 或者 multipart 混合。这几个东西任何一个处理不好,用户看到的就是一堆乱码或者一个打不开的附件。
我测过的坑里,最典型的是长主题折行。邮件头有长度限制,太长的主题会被折成多行,每行末尾加一个标记表示"下一行还是同一行"。如果解析代码没处理这个折行,主题就会被截断。还有附件文件名,中文文件名在不同客户端下可能变成乱码,因为编码方式不统一。这些细节在接口层面测不到,必须真的发一封邮件、真的用客户端收下来看。
5.2 FTP 主动与被动模式的端口差异
FTP 用两个连接:控制连接走 21 端口,数据连接单独开。主动模式下,服务端从自己的 20 端口主动回连客户端指定的端口;被动模式下,服务端开一个随机端口告诉客户端,让客户端来连。
这个差别在测试环境里的意义非常实际。客户端在 NAT 后面的时候,主动模式基本用不了,因为服务端回连的地址是内网地址,根本连不到。所以现在默认都用被动模式。但被动模式也有坑:服务端随机开的端口范围如果没在防火墙上放行,数据连接就建不起来,表现是"能登录、能列目录、但传文件卡住"。
这个"能登录但传不了文件"的现象是 FTP 问题的典型特征,因为控制连接和数据连接走的是不同的端口和方向。遇到这类问题,先确认模式,再确认端口范围,基本就能定位。
5.3 WebSocket、gRPC、MQTT 的测试手法差异
这三个协议都属于"长连接"或者"非请求响应"的范畴,测试思路和普通 HTTP 接口差别很大。我把关键差异列成表:
| 维度 | WebSocket | gRPC | MQTT |
|---|---|---|---|
| 承载 | 先 HTTP Upgrade 再切协议 | HTTP/2 承载 | 自有协议 |
| 数据格式 | 文本或二进制帧 | Protobuf 二进制 | 自定义负载 |
| 交互模式 | 双向随时发 | 一元、服务端流、客户端流、双向流 | 发布订阅 |
| 鉴权位置 | 握手阶段带上凭证 | 元数据里的 token | 连接时的用户名密码或证书 |
| 断言重点 | 帧内容、顺序、关闭码 | trailer 里的业务状态 | 投递次数、顺序、离线堆积 |
| 典型问题 | 断线重连、代理不支持 Upgrade | 状态码在 trailer 里被漏断言 | QoS1 重复投递导致业务重复处理 |
WebSocket 的握手是借 HTTP 完成的,请求头里带Upgrade: websocket和一对随机的 key,服务端返回 101 和计算后的 accept 值。测试的时候,握手阶段能不能带鉴权信息是个关键点,因为很多实现把 token 放在 URL 参数里,这会带来日志泄露的风险。另外断线重连必须专门测,包括网络闪断、服务端主动踢下线、长时间无数据被中间设备断开这几种情况,重连之后消息有没有丢、顺序有没有乱,都得覆盖。
gRPC 最容易踩的坑是断言。因为 gRPC 的业务状态码不在 HTTP 状态码里,而是在 trailer 里,HTTP 层永远是 200。如果自动化框架只看 HTTP 状态码,那所有业务错误都会被判成通过。这个坑我踩过一次,一批用例全绿,上线才发现错误处理逻辑根本没生效。
MQTT 的核心测试点在 QoS。QoS 0 最多一次,可能丢;QoS 1 至少一次,可能重复;QoS 2 恰好一次,开销最大。测 QoS 1 的时候必须验证业务侧有没有做去重,否则同一条消息处理两次,库存就扣两次。另外遗嘱消息和保留消息也要单独测,遗嘱是客户端异常断开时由服务端代发的,保留消息是新订阅者一连上就能收到的那条。
6. 把协议知识落成测试产出:抓包、Mock、断言
6.1 抓包过滤表达式速查
抓包工具本身不是重点,重点是你能不能在三秒内把无关流量过滤掉。全量抓包然后靠肉眼翻,效率低到不可接受。下面这些过滤表达式是我常用的,直接抄就行。
| 想看什么 | 过滤表达式 |
|---|---|
| 某个域名的 HTTP 请求 | http.host contains "api.example.test" |
| 只看 POST 请求 | http.request.method == "POST" |
| 某个状态码 | http.response.code == 502 |
| DNS 查询 | dns.qry.name contains "example.test" |
| TLS 握手里的 SNI | tls.handshake.extensions_server_name == "api.example.test" |
| 指定端口 | tcp.port == 8443 |
| TCP 重传 | tcp.analysis.retransmission |
| 连接被重置 | tcp.flags.reset == 1 |
| 握手失败告警 | tls.alert_message |
有两个使用习惯值得说。一是先抓小窗口再放大,别一上来就抓几万个包,先抓十秒、先按主机过滤,定位到大致范围再放开。二是抓包文件要留时间戳和场景标签,我见过太多"抓了但不知道是什么时候抓的"的包,等于白抓。
6.2 Mock 服务的分层设计
很多人理解的 Mock 就是"返回一个假的 JSON",这个理解太浅。真正好用的 Mock 是分层的,不同层解决不同问题。
最底层是传输层替换,改 hosts 或者起一个本地端口,把请求引到自己的服务上。这一层解决的是"目标服务还没好"的问题,优点是接入成本极低,缺点是只能整体替换,没法精细控制。
往上一层是协议层 Mock,自己起一个 HTTP 服务,能控制状态码、响应头、延迟、响应体大小、甚至故意返回半截然后断开。这一层解决的是"异常场景构造不出来"的问题。比如要测客户端对 504 的处理,真实环境里很难稳定复现,但 Mock 服务里加个 sleep 就能稳定复现。
再往上是业务层 Mock,针对第三方依赖打桩,返回预制数据集。这一层的关键是数据集要覆盖正常、边界、异常三类,不能只准备一份"标准成功响应"。我一般会准备至少五份:正常、空数据、字段缺失、类型错误、超长内容。
最高一层是故障注入,包括延迟、超时、连接复用异常、响应体截断、编码错误。这一层测的是客户端的健壮性,也是最容易漏测的部分。故障注入做得好,很多线上问题在测试阶段就能暴露。
这四层不是必须全做,但至少要有协议层和业务层,否则用例的覆盖面会很窄。
6.3 四层断言模型:从状态码到业务语义
断言写得好不好,直接决定用例有没有价值。我总结了一个四层模型,按覆盖深度递进。
第一层是传输层断言:状态码、响应时间、协议版本、响应头里的关键字段。这一层最基础,但也是最容易被写死的。比如把响应时间阈值写死成 500 毫秒,换台机器跑就全挂了。这类断言要留余量,或者改成相对比较。
第二层是结构层断言:字段是否存在、类型是否正确、必填项是否齐全、数组长度是否在范围内。这一层用 schema 校验比逐字段断言效率高得多,而且新增字段不会导致用例失败。
第三层是语义层断言:业务状态码是不是预期值、金额精度对不对、时间格式是不是约定的格式、枚举值是不是在允许集合里。这一层才是真正验证业务的,前面两层只是保证"能解析"。
第四层是行为层断言:幂等性、重试后的最终状态、并发下的数据一致性、消息顺序。这一层最难写,但价值最高,因为它测的是系统在真实压力下的行为。
我见过不少团队的用例只做到第一层和第二层,结果就是"用例全绿但线上照样出问题"。补断言这件事的投入产出比其实很高,加一条幂等断言可能只要十分钟,但能挡住一类线上事故。
7. 几个我反复踩过的坑,和怎么绕开
7.1 长连接被中间设备悄悄掐断
这个坑我在三个不同的项目里遇到过。客户端建了连接池,服务端也没问题,但中间的网关或者负载均衡配了空闲超时,比客户端的保活间隔短。结果就是连接看起来还在,写进去的数据石沉大海。
判断方法是在客户端和服务端同时抓包,对比时间线:客户端认为连接还在的时间点之后,服务端有没有收到数据。如果没有,那就是中间断了。
绕开的办法不是把超时调大,而是让客户端的保活间隔小于中间设备的空闲超时。中间设备的超时通常是运维配置的,你得先去问清楚是多少,再定客户端的参数。这个"先问清楚再定参数"的顺序很重要,我见过有人凭感觉把保活设成 30 秒,结果中间设备的超时是 15 秒,照样断。
7.2 重定向和鉴权头的配合
302 跳转的时候,浏览器会怎么处理 Authorization 头?答案是看情况,不同实现不一样,而且跨域的时候通常会丢掉。如果你的自动化用例依赖重定向后仍然带着凭证,那大概率会失败。
我的建议是自动化用例里一律关闭自动重定向,自己手动跟随,每一步都显式带上需要的头。这样虽然麻烦一点,但行为可控,出问题也好定位。生产环境里更要避免依赖重定向传递敏感头,用一次性的跳转票据代替。
7.3 缓存头配错导致测试互相污染
测试环境里如果开了缓存,前一条用例写入的数据可能被后一条用例读到旧值,表现就是"用例单独跑都过,一起跑就挂"。这类问题的根源往往是缓存头没配对,或者 CDN 层缓存了本不该缓存的响应。
绕开的办法有两个方向:一是在测试请求里带上绕过缓存的标记,让服务端返回最新数据;二是在用例设计上保证数据隔离,每条用例用独立的数据标识。前者见效快但治标,后者麻烦但治本。我倾向于两条都做,先用前者保证用例稳定,再慢慢把数据隔离补上。
7.4 时间相关的用例不能靠 sleep
用固定 sleep 等待异步结果,是所有测试里最脆弱的写法。机器快一点就白等,机器慢一点就失败。正确做法是轮询加重试上限,同时监控轮询次数,次数异常高说明系统本身有问题,不该靠调大重试次数掩盖。
还有一个和时间相关的细节:断言里如果涉及"当前时间",一定要用服务端返回的时间做基准,不要用本地时间。两端时钟只要差几秒,跨天的用例就会失败,而且失败信息看起来毫无道理。
我个人在实际操作中的体会是,应用层这块知识最容易陷入"看懂了但用不上"的状态。看文档的时候每条都明白,真到了要设计用例、要定位问题的时候,脑子里却调不出来。后来我改了个方法:每学一个协议,就强迫自己回答三个问题——它在什么情况下会慢、在什么情况下会错、在什么情况下会被中间设备改掉。这三个问题回答得出来,这个协议才算真的进了脑子。比如 HTTP 的"会被改掉",答案就是各种代理会加上或剥离X-Forwarded-For、会改写Connection、会压缩或者重新编码响应体;DNS 的"会慢",答案就是递归链路长、CNAME 套太多层、缓存没命中。这些答案不是为了背给面试官听的,是为了你在工位上看到异常现象时,能立刻想到该去哪儿看一眼。
最后再分享一个小技巧:复习到某个协议卡住的时候,别急着去搜"XX 协议详解",先打开抓包工具,自己发一个请求,把报文一层层展开看。原始报文里能看出来的东西,比任何教程都直观。看完了再回去读文档,理解速度会完全不一样。