
最近我把 atest 升级到了 v0.0.18本来只是想修一修之前用顺手的老接口结果发现新版本在 HTTP API Mock 这块做得相当完整。先说说结论如果你正在做前后端联调、接口测试、依赖第三方服务的开发或者需要在 CI 里搭建一套可重复的测试环境那么 atest v0.0.18 这套 Mock 能力可以直接拿来用不需要另外折腾一堆脚本和框架。以前我们联调最怕什么后端接口还没写好、第三方支付回调无法在本地模拟、测试环境的数据库被同事改得乱七八糟。为了解决这些问题我试过用 Node 写临时 mock server、用 JSON Server、用 Postman Mock但总觉得要么太轻、要么太重。atest 的定位不太一样它本身是个接口测试工具Mock 只是它的一部分能力但恰恰是这种“测试工具顺手把 Mock 做了”的设计反而让整个联调流程变得更加自然。这篇文章我会从设计思路、实操配置、问题排查这几个角度把这个版本的 Mock 能力拆开讲清楚希望能帮你少踩几个坑。1. 为什么要在项目里引入 HTTP API Mock1.1 没有 Mock 时联调有多难受说个很常见的场景前端页面已经写完了后端接口还在开发中。前端同学只能自己用假数据写在代码里写完之后还要反复和后端对字段名、对类型、对嵌套结构。后端一改字段前端就要跟着改一遍改完本地能跑一接真实环境又出问题。这种状况下大家其实都被迫做了一件很没效率的事情——靠“沟通”来维持接口一致性而不是靠“工具”来提前把问题暴露出来。再往外延伸一层测试同学也会遇到麻烦。接口自动化测试需要跑在稳定的环境上可测试环境的服务经常挂、数据经常变。如果直接对着测试环境写用例今天能过明天不能过排查起来还分不清是代码问题还是环境问题。这时候一个能在本地随时启动、可控、可重置的 Mock 服务就变得特别重要。我在 before 一版 atest 里也用过它的基础 Mock 功能但 v0.0.18 明显补上了很多生产环境需要的细节——状态机、动态端口、响应头覆盖、基于时间区间的响应、从真实服务回放数据。这些能力加在一起才敢说它“强大、灵活”。1.2 atest 为什么适合当 Mock 工具先说清楚一个概念HTTP API Mock 并不是“造假数据”这么简单。一个好的 Mock 服务至少要满足三个条件。第一规则可复用。别人能通过配置读懂你 Mock 了什么场景不用看一堆看不懂的脚本。第二能力可编程。同一个接口要能根据请求参数、请求头、当前状态返回不同结果而不是所有请求都返回一份固定 JSON。第三运行时可控。启动方式要轻量端口要灵活能在本地和 CI 里随时拉起和关闭。很多团队自己写 Mock 服务一开始确实挺爽两三天后就会发现问题什么鉴权逻辑、动态字段、状态流转、超时模拟全都要自己造轮子。造到一半你会发现这根本不是在做联调而是在做一个新的业务系统。atest 的设计思路是“用配置描述 Mock 规则”而不是“用代码实现 Mock 逻辑”这两者有本质区别。代码实现的 Mock逻辑越写越多越写越复杂最后没人敢改配置描述的 Mock规则清晰、变更可控任何人打开 YAML 文件就能知道当前有哪些场景。v0.0.18 把这种“配置化”的思路贯彻得更加彻底这也是我愿意把它写成一篇文章来分享的原因。2. 读懂 atest v0.0.18 的 Mock 设计思路2.1 用描述式配置替代脚本代码第一次使用 atest 的 Mock 功能最先要接受的一个理念转变就是不要写代码写描述。整个 Mock 服务的规则都放在测试描述文件里常见格式是 YAML。你不需要学一门脚本语言不需要维护一堆函数和类只需要把你希望 Mock 服务“怎么表现”描述出来。举个例子一个最简单的 GET 接口 Mock配置大概长这样mock: - name: 用户详情接口 request: method: GET path: /api/users/1001 response: status: 200 headers: content-type: application/json body: | { id: 1001, name: 张三, role: admin }我特意没有把字段写得特别复杂因为核心是想说明一条 Mock 规则 请求匹配条件 响应内容。请求怎么匹配响应怎么返回全部写在配置里。有人可能觉得这不是和很多工具一样吗区别在细节。v0.0.18 里请求匹配条件不止是“路径完全相等”还可以用通配符、正则、请求头匹配、请求体匹配。响应也不止是“返回一份固定 JSON”可以动态引用请求参数、可以覆盖响应头、可以按时间区间给不同结果、甚至可以转发到真实服务。这些能力组合起来就不是“写一个假接口”这么简单了而是一个可编程的接口模拟环境。2.2 状态机建模让 Mock 有“状态”很多接口并不是一次请求就能完成的而是需要在一个业务流程里一步步流转。比如一个订单系统从“待支付”到“已支付”到“已发货”再到“已完成”每一步都需要前端调不同的接口甚至同一个查询接口返回的状态还要跟着变化。如果在普通 Mock 工具里你只能写死一条规则查询订单永远返回“待支付”。那前端想测“已发货”之后的页面就完全没法做了。atest v0.0.18 的状态机 Mock 就是来解决这个问题的。你可以给同一个接口定义多个状态每个状态对应不同的响应内容并且定义状态之间的流转条件和顺序。前端测试“支付成功后的订单详情”Mock 服务先把当前状态切到“已支付”再响应对应的订单数据。我把这个能力理解成“给 Mock 装了一个记忆体”。没有记忆体的 Mock只是机械地返回预设数据有记忆体的 Mock才能模拟真实服务的业务流程。对于联调和自动化测试来说这个能力几乎决定了 Mock 工具的上限。2.3 从“返回固定数据”到“返回上下文数据”另一个让我觉得 v0.0.18 灵活的地方是响应内容可以动态绑定请求上下文。简单来说Mock 服务收到一个请求后可以从 URL 路径参数、查询参数、请求头、请求体里取出值再把这些值拼到响应里。比如这样一个接口GET /api/users/{id}你希望不管 id 传什么值接口都返回一个结构一致的用户对象并且 id 就是请求里的那个值。用普通 Mock 你只能一个 id 写一条规则id 一多配置就爆炸。用动态绑定一条规则就够了mock: - name: 动态用户查询 request: method: GET path: /api/users/{id} response: status: 200 body: | { id: {{id}}, name: 用户{{id}}, avatar: /avatars/{{id}}.png }这里的{{id}}就是在运行时从请求路径里取出来的动态值。类似的也可以绑定{{query.page}}、{{header.token}}、{{body.name}}这样的上下文变量。这种能力的价值在于你只需要写一条规则就能覆盖一类请求。配置量大幅减少逻辑也更加清晰。团队里如果有人想加一个新的用户 id 来测试完全不用改 Mock 配置直接调接口就行。3. 五个核心 Mock 能力逐个落地3.1 动态端口分配不让测试“抢车位”第一个值得单独说的能力是动态端口。早年间我在 CI 里跑接口测试最头疼的问题就是端口冲突。项目里有多个测试任务并行跑每个都要启动一个 Mock 服务端口一旦固定写死第二个任务就起不来。atest 的做法是支持端口设为 0让操作系统自动分配一个空闲端口。启动 Mock 服务后工具会把实际监听的端口打印出来测试脚本读取到这个端口再把请求发过去。atest mock run --config mock.yaml --port 0启动日志里会出现类似mock server listening on 127.0.0.1:53217的信息这 53217 就是本次随机分配的端口。测试脚本里可以解析这个端口也可以让 atest 把端口写入一个临时文件后续命令再从中读取。这个设计看起来很细节但实际用起来感受非常直接本地联调你可以同时起好几个 Mock 服务互不干扰CI 里并行跑任务也不用再排队等待同一个端口释放。联调效率提升不是体现在某一个功能上而是这些细节叠在一起的结果。3.2 状态机 Mock模拟一单订单的完整生命周期状态机是 v0.0.18 的亮点能力我单独花点篇幅说清楚。假设有一个订单系统前端需要验证“订单创建 - 支付成功 - 商家发货 - 交易完成”的全流程展示。如果用普通 Mock你最多把每个接口的固定返回写出来比如“创建订单”返回 pending“查询订单”也返回 pending。但前端要测“支付成功后的页面”就永远测不到因为支付接口没有被真实调用订单状态也不会自己变。状态机 Mock 的思路是这样同一个订单资源定义几个状态每个状态里给出这个资源的表现形态。同时定义触发事件和状态迁移关系。stateful: - resource: /api/orders/1001 initialState: pending states: pending: body: | {id: 1001, status: pending, amount: 99.00} paid: body: | {id: 1001, status: paid, amount: 99.00, paidAt: 2025-01-01 12:00:00} shipped: body: | {id: 1001, status: shipped, amount: 99.00, trackingNo: SF1234567890} transitions: - event: payment.success from: pending to: paid - event: order.ship from: paid to: shipped这里我把配置简化成了核心结构实际使用中你可以在触发事件时通过一个接口或者命令推进状态。前端在测试支付流程时可以调用“模拟支付成功”的事件接口让订单从 pending 变成 paid再触发发货事件订单从 paid 变成 shipped。每一步前端看到的数据都和真实业务里一致。我当时体验这个功能时最大的感受是终于不用再写一堆临时的“测试专用接口”了。以前为了模拟订单状态测试环境里得专门留一个后门接口来改数据。现在Mock 服务自己就能扮演这个后门而且是有状态流转的后门安全又干净。3.3 响应头覆盖测出“错误设计”的问题响应头看起来不如 body 显眼但真正做联调和测试的人都知道很多隐蔽问题就藏在响应头里。比如接口文档里写的是Content-Type: application/json; charsetutf-8Mock 返回的却是text/plain比如跨域场景下前端需要特定的Access-Control-Allow-Origin头才能正常请求再比如某些接口靠Set-Cookie做登录态Mock 里不模拟这个头前端联调就做不了。v0.0.18 里你可以在每条 Mock 规则里显式声明 response headers并且这些配置会覆盖默认行为。mock: - name: 带自定义头的接口 request: method: GET path: /api/v1/products/1 response: status: 200 headers: content-type: application/json; charsetutf-8 x-request-id: mock-12345 access-control-allow-origin: * body: | {id: 1, name: 示例商品}这里我额外加了x-request-id平时联调时可以用来和真实日志做关联。你也可以故意返回一个错误的、奇怪的响应头比如把Content-Type设成application/xml来验证前端代码对“非预期响应”的容错能力。这种“故意设错”的测试在普通环境里很难做在 Mock 里只需要改一行配置。3.4 基于时间区间的响应把超时和慢接口搬进测试时间是一个经常被忽略的测试维度。有些接口在工作日访问量高响应慢有些活动接口在零点前后返回逻辑不同有些订单系统在结算时间段会拒绝下单。要在真实环境里验证这些行为非常困难因为你控制不了时间。atest v0.0.18 提供了基于时间区间的响应配置你可以给同一个接口定义多个时间窗口不同窗口返回不同内容。mock: - name: 营销活动接口 request: method: GET path: /api/v1/activity response: timeRange: - start: 09:00:00 end: 18:00:00 status: 200 body: | {activityOpen: true, message: 活动进行中} - start: 18:00:01 end: 23:59:59 status: 200 body: | {activityOpen: false, message: 活动已结束}除了按时钟时间响应你还可以让接口故意延迟一段时间再返回用来模拟慢接口和超时场景。比如前端有个“加载超过三秒就显示超时重试”的逻辑你在 Mock 里把某个接口的响应延迟设置为 5000ms前端就能稳定地触发这个分支。这个能力对前端开发者尤其友好。以前想测“接口超时”只能靠浏览器开发者工具里的网络限速模拟或者临时改代码。现在直接在 Mock 配置里加一个延迟参数测试完删掉就好联调代码一行不用改。3.5 Mock 数据来自真实服务从 http/grpc 回放最后这个能力我一开始以为只是个“锦上添花”的功能真正用过之后才发现它的价值很大Mock 规则里的响应内容可以来自一个真实的 http 或者 grpc 服务。说个实际场景你依赖了一个第三方开放平台接口这个平台偶尔不稳定但你需要稳定的开发环境。正常的思路是记录一份真实响应把它固化在 Mock 里。可问题是第三方接口的返回结构可能会变化你手工复制一次数据过段时间可能就对不上了。v0.0.18 的思路是让 Mock 服务在启动后按照规则把请求转发到真实第三方服务把真实响应缓存下来下次同样的请求就直接返回缓存。这样既保证了首次联调的数据是真实可信的又避免了第三方不稳定带来的干扰。mock: - name: 第三方天气接口 request: method: GET path: /api/weather upstream: target: https://open.example.com/weather cache: true这个配置的含义是第一次请求到了 MockMock 把它转发给https://open.example.com/weather拿到响应后暂存后续相同的请求直接返回暂存的数据。你也可以手动清理缓存让 Mock 重新去真实服务拉取一次。这就是我前面说的“从真实服务回放数据”。它既保住了真实数据的样子又把不稳定因素挡在了开发环境之外。我在做一个支付模块联调时用过这个功能先把真实支付网关的返回抓下来再在 Mock 里反复测试前端各种分支几个小时都没再被网关波动打断过。4. 编排到日常流程本地开发 / CI / 联调环境4.1 Windows 下快速落地zip 包免安装很多人第一次接触这类命令行工具会担心环境配置麻烦尤其是 Windows 用户。atest 官方提供了 zip 格式的发布包解压即用不需要安装依赖不需要配置环境变量也不强制要求注册系统服务。我自己的习惯是把 zip 包解压到一个固定的tools目录下然后在终端里临时指定路径运行。# 解压到 D:\tools\atest D:\tools\atest\atest.exe mock run --config mock.yaml --port 18080如果你不想每次都输入完整路径可以把解压目录加进 Windows 的 PATH 环境变量。这一步对后续在命令行和 CI 脚本里调用会方便很多。atest 在这种细节上处理得比较接地气Windows 下可以直接用不用装一堆依赖包。有人可能觉得“免安装”没什么大不了但在团队内部推广工具时这恰恰是决定工具能不能真正落地的关键因素。让每个同事都搞一套 JDK/Node 环境再装依赖劝退率极高解压一个 zip 包就能用大家才愿意试一下。4.2 在 CI 里拉起 Mock 服务的思路Mock 服务在 CI 里最常见的用法是作为自动化测试的前置依赖。整个流程基本是这样的提交代码 - 构建项目 - 启动 Mock 服务 - 启动被测应用 - 运行自动化测试 - 关闭 Mock 服务。用 atest v0.0.18 来承担中间那一环有两个明显的优势。第一个是启动快。Mock 服务本质上是在内存里挂载规则不需要连接数据库不需要加载业务代码几秒钟内就能就绪。第二个是配置干净。所有 Mock 规则都在一个 YAML 文件里CI 里只需要指定配置文件不需要往代码仓库里塞一堆 mock 脚本。我见过一些团队把 Mock 服务做成 Docker 镜像在 CI 里靠容器启动。这在某些场景下没问题但如果只是跑一些轻量级的接口测试这样反而增加了镜像构建和容器编排的复杂度。直接在 CI 脚本里下载 atest zip 包解压后运行其实更快更省事。4.3 和其他 API 工具链的边界聊到这里可能有人会问那我是不是有了 atest 就可以全流程替代其他工具了我的看法是工具之间不是替代关系而是分工关系。像 Postman、Apifox 这类工具强在接口调试和文档管理团队里做接口梳理、基本调试很方便。而 atest 这类命令行测试工具强在自动化、可编排、可嵌入 CI。你可以继续用 Postman 做接口调试把成型的业务场景描述成 atest 的 Mock 规则和测试用例两者并行不悖。我之前一直用 platform-tools 类的工具链管理 Android SDK 环境也用过不少 Windows zip 形式分发的命令行工具。这类工具的统一特点是个体轻量、职责单一、组合灵活。atest 其实也是这条路线上的产物它不试图取代你的整个工具链而是作为一个可编程的“接口模拟与测试节点”嵌入到本地开发、联调和 CI 的任意位置。5. 常见问题与排查心得5.1 匹配不生效先查请求路径和通配符新手最容易遇到的问题是明明配好了 Mock 规则但请求就是匹配不上。我自己的排查顺序一般是这样的先看请求方法和路径是不是完全一致再看有没有使用通配符最后看是不是有两条规则的优先级冲突。如果你配置的是path: /api/users/{id}这种动态路径记得确认 atest 版本的路径参数语法。有些版本用{id}有些版本用:id写错了就匹配不到。遇到这种情况最快的方式是先加一条非常宽泛的兜底规则比如匹配所有GET /api/*请求先把请求路径打印出来再根据实际路径修正 Mock 规则。我之前踩过的一个坑是路径里多了个尾部斜杠。请求发的是/api/users/1001/配置里写的是/api/users/1001看起来差不多但对很多路由匹配来说就是两条完全不同的路径。如果你的 Mock 规则总是匹配不上优先检查这种“看不见”的差异。5.2 响应中文乱码显式声明 charsetMock 返回的中文在页面上显示成乱码这个问题很常见。多数情况是因为响应头里的Content-Type没有带charsetutf-8。浏览器或者 HTTP 客户端在没有明确字符集时会用自己的默认编码去解析 UTF-8 内容自然就会出现乱码。解决办法很简单在 Mock 规则的 response headers 里显式声明headers: content-type: application/json; charsetutf-8我建议从一开始就养成这个习惯不管你的项目里目前有没有中文内容。因为这个头一旦缺失等联调阶段才发现页面上的中文一片乱码排查起来还要绕一大圈消耗的时间完全不合理。5.3 状态机回滚与串联问题用状态机 Mock 的时候有一个细节特别容易忽略状态是“共享”的还是“每个请求独立”的。如果配置里定义了一个全局共享状态那么一次状态迁移会影响所有请求这个接口的客户端。这在单前端联调时没问题但如果 CI 里有多个测试用例并行跑一个用例把状态切到了“paid”另一个用例还在期待“pending”测试就会互相干扰。我的做法很简单在 CI 里每次跑测试之前先调用一次 Mock 服务提供的“重置状态”接口把状态机回到初始状态。在本地联调时如果多人共用一个 Mock 实例最好约定每个人用不同的资源 ID比如张三用/api/orders/1001李四用/api/orders/1002让状态机的实例按资源隔离而不是共享同一个全局状态。5.4 Mock 服务端口冲突端口冲突在老版本里偶有发生v0.0.18 支持动态端口后这个问题基本从根上解决了。但如果你的场景里必须使用固定端口比如防火墙只放行某一个端口建议在启动命令里加一个“端口占用检测”的判断。简单的方式是先检查端口是否可连通能连通就说明被占用了要么换端口要么先杀掉占用进程。在 Windows 下查看端口占用我习惯用netstat -ano | findstr 18080拿到 PID 之后再用taskkill /PID pid /F结束进程。不过这只是应急操作更好的做法还是尽量用动态端口让 Mock 服务自己找一个可用的端口启动。5.5 常见问题速查表我把上面这些经验整理成一个表格方便你在实际使用中快速对照现象常见原因解决办法请求总是匹配不到规则路径动态参数语法不对确认使用{id}还是:id中文响应乱码Content-Type 缺 charset显式加charsetutf-8状态机测试互相干扰状态全局共享重置状态或用不同资源 ID 隔离启动时提示端口被占用端口已被其他进程使用改用端口 0 动态分配Mock 返回数据不更新启用了真实服务缓存清理缓存或重新启动 Mock通配符规则不生效路径格式和请求不匹配加一条宽泛规则打印实际路径响应头覆盖不生效多个规则优先级冲突检查规则顺序优先精确匹配这张表我每次在团队里分享 Mock 使用经验时都会贴一遍很多问题其实几分钟就能定位只是第一次遇到时容易走弯路。最后再分享一个我自己的习惯不要把 Mock 规则写得太“聪明”。一个接口如果需要好几层动态判断那就说明它已经承担了太重的逻辑此时应该考虑是不是该让它保持“傻一点”复杂逻辑留给真实后端去测。Mock 服务的职责是稳定、可控地模拟真实服务的外部表现而不是把所有业务分支都复刻一遍。我现在做联调基本就是先启动一组固定的 Mock 规则前端和后端各自并行开发前端通过 Mock 验证页面逻辑后端通过真实接口测试跑自己的用例两边对不上时再互相 review 一下接口描述。整个过程里Mock 服务就是那个“稳定的中间人”atest 在中间承担的角色比我想象中靠谱不少。