ARTICLE DETAIL

资讯详情

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

JMeter天气接口自动化测试:参数化、关联与断言实战

JMeter天气接口自动化测试:参数化、关联与断言实战 1. 从天气接口切入为什么它适合当自动化测试的练手场做接口自动化很多人第一反应是拿公司内部业务系统开刀结果卡在鉴权、加密、环境不稳定上一周下来脚本没跑通几条。我的建议是先用一个外部公开的天气接口把 JMeter 的四大核心能力跑一遍——参数化、断言、关联、正则这四样练熟了再回头啃业务接口会顺得多。天气接口的好处很实在不需要登录态、响应结构稳定、字段含义一眼能懂、返回体里有嵌套的 JSON 和字符串 ID天然适合做提取和校验练习。这篇内容我按从零搭一套能跑的天气接口测试脚本来写覆盖 CSV 参数化驱动多城市、正则提取器做上下接口关联、多层级断言校验业务正确性以及一堆踩过的坑。适合两类人刚装完 JMeter 还在对着空白的测试计划发呆的新手以及写过几个接口脚本但断言写得糊里糊涂、参数化只会用最基础配置的老手。全文用的是一个两步走的场景——先按城市名查城市 ID再拿 ID 查实时天气这个链路里关联和正则才有用武之地。先把要用的工具和版本说清楚避免后面因为版本差异对不上界面。JMeter 我习惯用5.6.3它自带 JSON 断言和 JSON 提取器不用额外装插件JDK 用17JMeter 5.6 系列对 JDK 8 到 17 都兼容良好JDK 17 的启动速度比 8 明显快一些。天气数据源这边用心知天气的免费版接口举例它的城市搜索接口和实时天气接口是分开的正好构成关联场景。你手上如果有别的天气服务和风、OpenWeather 之类把 URL 和字段名替换掉即可思路完全一样。提示免费版接口一般有调用频率限制做参数化和循环的时候别把循环次数开太大几十次足够验证脚本逻辑别拿它去做压力测试。整体脚本的组织方式是一个线程组下面挂三个核心 Sampler 分支——HTTP 请求默认值统一收口域名和公共参数CSV Data Set Config 负责城市数据供给两个 HTTP 请求分别对应查城市 ID和查天气中间用正则提取器把 ID 传下去最后挂上三层断言。这个结构看起来朴素但它把配置的耦合度压到最低改域名只改一处换数据集只换一个文件。2. 测试计划骨架搭建与请求基础配置2.1 线程组参数该怎么算新建测试计划后第一件事是加线程组很多人随手填个线程数 1、循环 1 就完事了其实这两个数字要和后面的数据行数对齐不然参数化会跑出你意料之外的结果。我的习惯是按下面的公式倒推总请求数 线程数 × 循环次数数据消耗行数 总请求数在线程共享模式下每次采样消耗一行假设我的cities.csv里准备了 10 个城市那我就把线程数设成5、Ramp-Up 设成5、循环次数设成2。这样 5×210正好把 10 行数据消耗干净一个不剩一个不重。Ramp-Up 填 5 的意思是 5 秒内把 5 个线程全部启动起来每秒起 1 个避免所有线程同一瞬间打过去。如果是本地调脚本Ramp-Up 填 1 都无所谓但只要涉及外部接口稍微拉开一点启动间隔能显著降低触发限流的概率。调度器那一栏除非你想让脚本跑固定时长否则不要勾。勾了调度器之后循环次数会被忽略初学者最容易在这里懵掉——明明写了循环 2 次结果跑了几十次停不下来就是调度器在起作用。2.2 HTTP 请求默认值收口公共配置在测试计划下加一个HTTP Request Defaults配置元件里叫HTTP请求默认值把协议、服务器名称、端口填进去。这一步的价值在于后面每加一个 HTTP 请求协议和域名都不用再填改环境的时候只改这一个地方。以心知天气为例服务器名称填api.seniverse.com协议https端口留空HTTPS 默认 443JMeter 会自动补。这里有个细节端口栏留空和填 443 是等价的但如果你把协议写成 http 又留空端口它会走 80很容易出现 301 跳转结果断言拿到的是跳转页面而不是真实数据。另外把公共参数放进默认值也有讲究。像keyAPI 密钥、language、unit这类每个请求都要带的参数可以统一写进 HTTP 请求默认值的参数列表里。JMeter 的参数合并规则是请求自身的参数 默认值的参数同名的时候请求自身优先。所以你把公共参数放默认值个别请求要覆盖的时候在请求里重写一份就行不用复制粘贴一堆重复项。注意API 密钥属于敏感信息别直接硬编码在 JMX 文件里然后传到代码仓库。可以用${__P(apiKey)}从命令行或 user.properties 读取运行时用-JapiKeyxxx传入。2.3 两个请求的路径与参数设计第一个请求负责城市搜索路径写/v3/location/search.json参数是q城市名后面要用变量替换、key、languagezh-Hans。这个接口返回一个 JSON 数组里面每个元素带一个形如WX4FBXXFKE4F的 location id。第二个请求负责实时天气路径写/v3/weather/now.json参数是location这里填的就是第一个请求提取出来的 ID 变量、key、language、unitc。注意心知天气的实时天气接口既接受城市 ID 也接受经纬度我们走 ID 路线为的就是让关联逻辑成立。两个请求的 HTTP 方法都是 GET实现方式选参数列表模式而不是Body Data。参数列表模式 JMeter 会自动做 URL 编码中文城市名北京这种传进去不会出乱码Body Data 模式则需要你自己处理编码新手很容易在这里踩坑。在第二个请求下面挂一个察看结果树和聚合报告调试阶段全程开着跑通之后再把察看结果树关掉——它会把每个响应体都缓存在内存里长时间跑测试的时候会吃掉大量堆内存。3. 参数化CSV 数据驱动让一份脚本跑遍多个城市3.1 CSV Data Set Config 逐项拆解参数化的核心目的只有一个把脚本里写死的东西抽出来变成可以批量替换的数据。JMeter 里最常用的载体就是 CSV Data Set ConfigCSV 数据文件设置。先在脚本目录下建一个data/cities.csv内容就是一行一个城市名北京 上海 广州 深圳 杭州 成都 武汉 西安 南京 重庆然后在线程组下加 CSV Data Set Config几个关键字段我逐个说字段填什么为什么这么填Filenamedata/cities.csv相对路径以 JMX 所在目录为基准推荐用${__P(csvPath)}参数化方便换机器File encodingUTF-8中文城市名必须 UTF-8用 GBK 会出现乱码导致接口查不到城市Variable NamescityName一行一列时写一个名字就够多列用英文逗号分隔Ignore first lineFalse我的 CSV 没有表头所以不忽略如果有表头就填 TrueDelimiter,单列文件填什么都行但多列时必须和实际分隔符一致Allow quoted dataFalse数据里不含引号包裹的值关掉能避免解析歧义Recycle on EOFFalse数据用完后不再循环避免重复请求同一个城市Stop thread on EOFTrue数据耗尽就停线程让结果数正好等于数据行数Sharing modeAll threads见下一节的详细讨论Recycle on EOF和Stop thread on EOF这两个选项很容易被忽略但它们决定了脚本跑完之后的收尾行为。Recycle 关掉、Stop 开上得到的是一份干净的一次性消耗如果两个都不勾数据读完之后变量会保留最后一行的值继续跑结果是最后一个城市被反复请求报表里看不出问题但数据是脏的。3.2 线程共享模式与每个线程分块取值的实现Sharing mode 这个下拉框是 CSV 参数化里最容易被误解的地方它有三个选项All threads所有线程共享一个文件指针谁先读谁拿下一行。默认值就是它。Current thread group每个线程组各自维护一个文件指针多线程组场景下用。Current thread每个线程各自从头读一遍文件10 个线程读 10 行数据的话每个线程都会从第一行开始。很多人搜JMeter 在同一个 CSV 里让每个线程分块取值说的其实就是希望线程 1 拿第 1-4 行、线程 2 拿第 5-8 行这种互不干扰的切分。必须说明的是原生 CSV Data Set Config 做不到严格分块因为 All threads 模式下多线程是争抢式读取的读到的行号不由你控制。真要实现分块有两个能落地的做法我都实测过做法一按线程数拆分文件。把 20 行数据拆成 5 个文件cities_1.csv到cities_5.csv每个文件 4 行然后用函数拼接文件名${__CSVRead(${__P(dataDir)}/cities_${__threadNum}.csv,0)} ${__CSVRead(${__P(dataDir)}/cities_${__threadNum}.csv,next())}__threadNum返回当前线程编号从 1 开始配合文件名的数字后缀天然实现一个线程一个文件分块效果百分百可控。__CSVRead的第二个参数0表示读第一列的第一行next()表示读同一列的下一行。注意__CSVRead是函数不是配置元件它不参与变量替换的预编译运行时才读文件所以文件名里可以直接嵌函数。做法二用${__threadNum}做等差数列计算。用__intSum之类的函数算出起始行号但 CSV Data Set Config 并不支持按行号跳转所以这条路基本走不通不推荐浪费时间。拆分文件这个方案虽然土但胜在可控、可调试脚本跑完之后每行数据对应哪个线程一目了然。如果数据量特别大、拆文件不现实那就老老实实用 All threads 模式接受行数由调度顺序决定这个事实。3.3 数据库参数化的备选路线数据量上来之后CSV 的维护成本会变高——改一个值要开文件、找行、保存多人协作还容易冲突。这时候可以换成数据库参数化。思路是建一张城市表用JDBC Connection Configuration配置数据库连接池再用一条JDBC Request把数据查出来通过Variable Names把结果列映射成 JMeter 变量。配置要点如下JDBC Connection Configuration 里的Variable Name填cityPool这是连接池的引用名后面 JDBC Request 要对应。Database URL形如jdbc:mysql://127.0.0.1:3306/testdb?useUnicodetruecharacterEncodingutf8编码参数一定要带否则中文城市名会变问号。JDBC Request 的Query Type选Select StatementSQL 写SELECT city_name FROM city_list WHERE enabled 1。Variable Names填cityName意即把结果集的第一列映射到cityName变量。这里有个关键的坑JDBC Request 一次性把所有行都查出来并映射成变量时JMeter 会生成cityName_1、cityName_2……以及一个cityName_#记录总行数。它不会自动一行一行往下取。想让它像 CSV 那样逐行消费得在 SQL 层面自己控制——比如加LIMIT配合线程号计算偏移量或者用一条只返回一行的子查询。所以我的实际选择是调试期用 CSV跑批和多人协作时用数据库并且数据库场景下把 SQL 写得简单点一次取一行。指望 JDBC Request 干 CSV 的活最后大概率要写一堆 BeanShell 去做行号管理得不偿失。4. 关联与正则把第一个响应的结果喂给第二个请求4.1 什么时候必须做关联关联Correlation说白了就是上一个接口吐出来的东西下一个接口要用。最典型的三种场景会话令牌、业务主键、动态签名。天气接口这个例子里属于第二种——城市搜索返回的 location id 就是后续查天气必须的业务主键。判断要不要做关联有个很简单的检验方法把第二个请求单独拎出来用固定参数发一次如果它照样能返回正确结果说明不需要关联。但城市 ID 这种东西你会发现虽然北京万年不变是WX4FBXXFKE4F硬编码进去也能跑可一旦你要参数化到 10 个城市就必须动态提取了。硬编码的脚本本质上只能测一个城市扩展性为零。关联的实现位置在第一个 HTTP 请求的子节点上加一个后置处理器最常用的是Regular Expression Extractor正则表达式提取器JMeter 5.6 里还内置了JSON Extractor和Boundary Extractor。这篇以正则为主线JSON 提取器放在后面做对比。4.2 正则表达式提取器的五个字段正则提取器的界面就五个输入框但每一个都有讲究字段我填的值说明Name of created variablecityId后面用${cityId}引用Regular Expressionid\s*:\s*([A-Z0-9])提取括号捕获组里的内容Template$1$表示取第一个捕获组$0$是整个匹配Match No.1取第一个匹配0表示随机-1表示全部Default ValueNOT_FOUND匹配失败时的兜底值强烈建议填方便断言直接抓分工解释一下正则的每一段id精确匹配字段名避免误抓到location_id之类\s*:\s*容忍冒号前后的任意空格服务端返回的 JSON 美化后经常带空格([A-Z0-9])是捕获组限定为大写字母和数字比万能的(.?)更精确能有效防止抓串。为什么不用(.?)因为它太贪婪了一旦响应体里有多处引号结构很容易把不该抓的东西吞进去。限定字符集是最便宜也最有效的防错手段。实测中id:(.?)在返回体包含location:{id:xxx,name:yyy}时通常也能抓对但万一某个字段的值里含引号就会失控。4.3 模板、匹配数字和取全部结果的用法Template 的写法必须带$符号写成$1$而不是1写成1的话 JMeter 会把字面量1当成结果传下去非常隐蔽。如果要拼多个捕获组可以写$1$-$2$中间加分隔符比如把省份和城市拼起来。Match No. 的三个取值决定了变量长什么样1只取第一个匹配结果是单纯的cityId。0随机取一个匹配适合从候选列表里随机挑。-1取所有匹配此时会额外生成cityId_1、cityId_2……cityId_N还有一个cityId_matchNr记录总数。注意这时候cityId本身的值是不确定的不同版本行为略有差异要引用具体项必须写${cityId_1}这种带下标的变量。什么时候用-1比如一个接口返回了多个候选城市你想拿第二个。或者你想验证返回结果的数量是否等于请求数量用cityId_matchNr做断言就非常直接。4.4 JSON 提取器和正则的取舍JMeter 5.6 内置的 JSON Extractor 用的是 JSONPath 语法提取城市 ID 只要一行$.results[0].id对比一下正则在同样场景下的表现维度正则提取器JSON 提取器上手难度需要懂正则语法需要懂 JSONPath对格式的容忍度高字段顺序变化不影响要求响应必须是合法 JSON性能文本扫描响应体特别大时略慢解析器解析结构化数据下更快提取数组需要开 Match No. -1用$..id或$[0,1]更自然跳到父节点做不到用$..递归下降可以我的选择标准是响应是规整 JSON 就用 JSON 提取器响应是 HTML、XML 或者夹杂着乱七八糟格式的文本才上正则。正则的真正价值不在 JSON 场景而在于处理那些没有结构保证的返回——比如从一段 HTML 里抠出隐藏字段的 token或者从带时间戳的文本里截取日期。顺便说一句正则里有个容易被忽略的设定.默认不匹配换行符。如果响应体里的目标字段跨行需要显式开启Dot matches newline选项JMeter 正则提取器界面上没有这个勾选框得在 pattern 里写(?s)内联标志。这个坑我在抓一份格式化过度的 XML 时踩过正则怎么看都对就是不匹配加了个(?s)立刻好了。正则调好了别急着往下走先用察看结果树里的RegExp Tester面板验证一遍。在结果树里选中第一个请求的响应切到 RegExp Tester 标签把 pattern 和 template 粘进去点 Test能直接看到提取出的值。这一步能省掉后面一大半的排查时间。5. 断言让脚本自己判断对错5.1 响应断言做第一层把关没有断言的自动化脚本等于没写。JMeter 默认只要请求返回 200 就认为通过但接口返回 200 却带着错误码的情况太常见了——参数缺失、配额耗尽、密钥过期服务端往往也是 200 状态码加一个 error 字段。所以断言必须显式加上。Response Assertion响应断言是最基础的一层我的配置是Apply to 选Main sample onlyField to Test 选Response TextPattern Matching Rules 勾ContainsPatterns to Test 填results和location之所以用Contains而不是Matches因为Matches要求整个响应体完全等于你给的那个正则对 JSON 响应来说根本不现实。Contains是子串包含匹配到就算通过。同时我会再加一条针对 HTTP 状态码的断言Field to Test 改成Response CodePattern Matching Rules 勾EqualsPatterns to Test 填200这条断言的作用是防止某些服务端异常时返回 4xx/5xx 但被默认逻辑放过。加上它之后任何非 200 的响应都会直接把采样标记为失败。5.2 JSON 断言和 BeanShell 断言怎么选JSON AssertionJSON 断言是 JMeter 4.0 之后内置的配置极简填一个 JSONPath 表达式勾上Additionally assert value就能对比期望值。用它校验城市名称可以这么写Assert JSON Path exists$.results[0].location.name勾选Additionally assert valueExpected Value 填北京但这里有个硬伤变量替换在 JSON 断言里不生效的地方比你想的多。期望值那一栏填${cityName}时不同版本的 JMeter 表现不一致有的版本能替换有的版本会当成字面量。稳妥的做法是别在 JSON 断言里比对参数化变量而是把响应里包含参数值这件事交给 BeanShell 断言。BeanShell 断言是万能兜底方案本质上就是一段 Java 代码返回值决定断言成败。典型用法import org.json.JSONObject; String resp prev.getResponseDataAsString(); JSONObject obj new JSONObject(resp); String respCity obj.getJSONArray(results) .getJSONObject(0) .getJSONObject(location) .getString(name); String reqCity vars.get(cityName); if (!respCity.equals(reqCity)) { Failure true; FailureMessage 城市不匹配请求 reqCity 返回 respCity; }这段代码里几个关键点prev是内置对象代表上一个采样结果vars用来读 JMeter 变量Failure和FailureMessage是两个约定的输出变量把Failure设为true就表示断言失败FailureMessage的内容会出现在结果树的失败详情里。实测下来BeanShell 断言的性能开销不小它每次都要启动解释器执行脚本。如果你的并发场景下断言量很大建议改用JSR223 Assertion Groovy语法几乎一样但 Groovy 会编译缓存速度快一个数量级。5.3 一套能落地的断言分层规范断言写多了容易乱我的做法是按层次划分每层只关注自己该管的事层级检查内容用的元件失败代表什么协议层状态码、响应头 Content-Type响应断言网络或服务端异常业务层status/code 字段、results 是否存在响应断言、JSON 断言业务逻辑拒绝数据层字段类型、取值范围、与请求参数一致BeanShell/JSR223 断言数据错乱或串号性能层响应时间是否超过阈值Duration Assertion性能劣化性能层的Duration Assertion持续时间断言特别值得单拎出来说填一个毫秒数超过就算失败。它把接口自动化测试和性能测试的边界打通了——你不需要专门跑一次压测日常自动化里挂上它就能在功能回归的同时守住一个粗粒度的响应时间红线。填多少合适看接口的历史 P95 响应时间乘个 1.5 到 2 倍当阈值比如天气接口平时 300ms 左右我就填 800ms。还有一个反模式要提醒别用响应文本包含 xxx这种断言去校验时间字段。时间每次都在变断言必然失败或者被你改成永远通过的空壳。时间类字段要么用正则匹配格式比如\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}只验格式不验值要么干脆不验。6. 常见问题与排查技巧实录6.1 中文乱码和参数未替换跑参数化最常见的第一类问题是中文变问号表现为接口返回城市不存在。根因通常有两个CSV 文件存成了 GBK 但没有在 CSV Data Set Config 里指定编码或者 HTTP 请求没有指定内容编码。排查顺序是先确认 CSV Data Set Config 的 File encoding 是UTF-8再确认 CSV 文件本身真是 UTF-8用记事本另存为的时候注意选编码最后在 HTTP 请求里把 Content encoding 填上UTF-8。如果还不行打开jmeter.properties找到sampleresult.default.encoding把注释去掉并改成UTF-8。第二类问题是变量没有被替换请求里原样发出了${cityName}这个字符串。检查清单变量名在 CSV Data Set Config 和引用处必须完全一致JMeter 变量是大小写敏感的cityname和cityName是两个东西CSV 文件路径如果用了相对路径要确认它是相对于 JMX 文件所在目录如果引用的是正则提取出来的变量得确认提取器挂在了正确的采样器下面子节点顺序错了会拿不到。6.2 正则匹配不到的四个原因正则匹配失败是最耗时间的问题我总结出四个高频原因驼峰或大小写不一致id和Id完全匹配不上先把响应体复制到文本编辑器里搜一遍确认字段名。响应体里有转义字符JSON 字符串里如果含\正则里的引号就得跟着调整或者干脆改用 JSON 提取器绕开这个问题。跨行匹配失败前面说的.不匹配换行pattern 前面加(?s)可解决。Match No. 设成了超出范围的数字比如响应里只有一个匹配你写了2结果就是取不到返回 Default Value。调试正则的黄金动作是把响应体原封不动复制出来用文本编辑器的正则搜索功能试一遍。编辑器里能搜到JMeter 里基本就能提取到编辑器里搜不到就别在 JMeter 里反复改配置了。6.3 断言误报的几个陷阱断言把正确的响应判成失败这种误报比漏报更让人抓狂因为它会污染整个测试报告的通过率。几个典型陷阱JSONPath 对数组写法不兼容。$.results[0].id和$.results.0.id在不同版本里支持度不一样统一用方括号写法最保险。响应做了 gzip 压缩。JMeter 默认会自动解压但如果你在 HTTP 请求里手动加了Accept-Encoding: gzip又没有相应的解码配置断言拿到的是二进制流必然失败。让 JMeter 自己处理压缩头别手动加。断言作用域挂错。断言设在测试计划层级会对所有采样器生效设在某个采样器下只对它生效。层级越高误伤的采样器越多。我一般把断言设在最贴近的那个采样器下。Duration Assertion 在首次运行时误报。第一次请求要建立 TCP 连接和 TLS 握手耗时天然比后续请求长。做性能基线的时候记得跳过或者单独标记第一次采样。6.4 压力场景下的连接异常做并发的时候会遇到java.io.IOException: error writing to server这类报错采样器直接标红。这个报错的本质是客户端在往连接里写数据的时候连接已经被对端关闭了。常见成因有三个服务端因为频率限制主动断连、HTTP 长连接空闲超时被回收、本机端口耗尽。对应的处理手段在 HTTP 请求的Implementation里选HttpClient4它对连接复用的处理比默认的 Java 实现更稳。调整jmeter.properties里的httpclient4.time_to_live把连接存活时间设置得短一点避免拿到已经被服务端回收的死连接。关掉KeepAliveHTTP 请求里勾选Use KeepAlive取消代价是每次都要重新握手但能规避死连接问题。这个取舍要看测试目标测吞吐量就开 KeepAlive测稳定性可以关掉。另外提醒一句JMeter 的 GUI 模式只适合调试正式跑并发必须用命令行模式jmeter -n -t xx.jmx -l result.jtlGUI 自身的渲染开销会严重扭曲测试结果尤其是 100 并发以上的场景。报告用-e -o report_dir参数直接生成 HTML比在 GUI 里看聚合报告准确得多。6.5 问题速查表现象最可能的原因处理动作请求里出现${xxx}字面量变量名拼写或大小写不一致逐字比对 CSV 与引用处中文参数变问号文件编码或请求编码非 UTF-8CSV 编码设 UTF-8请求加 Content encoding正则提取返回 Default Value字段名不匹配或跨行用 RegExp Tester 现场验证断言全红但响应看着正常JSONPath 写法或 gzip 干扰换方括号写法去掉手动 gzip 头数据行数和请求数对不上Sharing mode 或 Recycle 配置不当关 Recycle开 Stop thread on EOF并发时报连接写入失败长连接被服务端回收换 HttpClient4缩短连接 TTL结果树里响应很慢GUI 模式开销大改用命令行模式执行7. 我在实际项目中沉淀的几个习惯第一个习惯是每次改脚本先跑一次单线程。参数化、断言、关联这些东西组合起来之后出错的地方可能有三四处一上来就多线程跑失败日志混在一起根本没法定位。先把线程数设成 1、循环 1确认链路通了再往上加。第二个习惯是给每个采样器起有意义的名字。默认名字是HTTP请求、HTTP请求1跑到第五个请求你自己都分不清谁是谁。我一般按动作_对象的格式命名比如查城市ID_北京、查天气_按ID看报表的时候一眼就明白。第三个习惯是把 Default Value 当断言用。正则提取器的 Default Value 我固定填NOT_FOUND然后在断言里加一条响应文本不包含 NOT_FOUND。这样一来即使后面的关联请求因为变量没取到而返回了乱七八糟的结果也能在最前面的那一步就被拦下来失败原因直接指向提取环节排查路径缩短一大半。第四个习惯是用__P把环境相关的配置全部外置。域名、密钥、CSV 路径、数据库连接串全都写成${__P(xxx,默认值)}的形式运行时用-J参数传入。同一份 JMX 文件可以在本地、测试环境、预发环境之间无缝切换不用改一行脚本。这套做法在团队协作里的价值尤其明显——别人的机器上路径和你的不一样参数化之后就不用各自维护一份分支了。这套天气接口的练习脚本从最开始只能跑一个城市到后来支持 10 个城市参数化、两层断言、动态关联我前后迭代了三四版。真正花时间的从来不是把元件拖进测试计划而是想明白每个配置项背后的行为逻辑。你把上面这些坑都走一遍再去看那些业务系统的接口测试会发现套路基本一致——无非是多了一层登录态和签名计算而已。
返回列表