news 2026/9/13 8:31:48

Hurl 请求链实战:在单个 Hurl 文件中串联多个请求并用 JSONPath 断言 REST API

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hurl 请求链实战:在单个 Hurl 文件中串联多个请求并用 JSONPath 断言 REST API

Hurl 请求链实战:在单个 Hurl 文件中串联多个请求并用 JSONPath 断言 REST API

【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl

本篇指南基于 Hurl 官方教程中的 "Chaining Requests" 章节,演示如何在同一个.hurl文件中按顺序组织多个 HTTP 请求:从追加第二个请求开始,逐步扩展到测试返回 JSON 的 REST API、使用[Query]参数段、应用类型化谓词与count/regex过滤器。读完后,你将能够编写一个包含多个 entry 的完整 Hurl 测试文件,理解hurl --test的执行语义,并掌握对 JSON 响应做精确断言的各种写法。

教程上下文:从 basic.hurl 继续

本教程承接前两步(你的第一个 Hurl 文件 与 添加断言)。在开始请求链之前,basic.hurl里只有一个请求,已经对首页 HTML 与响应头做了大量断言:

# Checking our home page: GET http://localhost:3000 HTTP 200 [Asserts] xpath "string(//head/title)" == "Movies Box" xpath "//h3" count == 2 xpath "string((//h3)[1])" contains "Popular" xpath "string((//h3)[2])" contains "Featured Today" # Testing HTTP response headers: header "Content-Type" == "text/html; charset=utf-8" cookie "x-session-id" exists cookie "x-session-id[HttpOnly]" exists

此时只有一个 HTTP 请求,但响应上已经积累了大量测试。教程强调的原则是:断言写得越多,测试套件就越不易脆弱。接下来要把其他请求也加进来,继续增加测试。

追加第二个请求:同一文件中的多个 entry

在同一个文件里,直接在第一请求之后写第二个请求即可。这里验证目标:对坏链接,服务器应返回 404 页面(404 是 HTTP 标准的 "Not Found" 状态码,语义可参考 MDN 文档)。

修改后的basic.hurl

# Checking our home page: GET http://localhost:3000 HTTP 200 [Asserts] xpath "string(//head/title)" == "Movies Box" xpath "//h3" count == 2 xpath "string((//h3)[1])" contains "Popular" xpath "string((//h3)[2])" contains "Featured Today" # Testing HTTP response headers: header "Content-Type" == "text/html; charset=utf-8" cookie "x-session-id" exists cookie "x-session-id[HttpOnly]" exists # Check that we have a 404 response for broken links: GET http://localhost:3000/not-found HTTP 404 [Asserts] header "Content-Type" == "text/html; charset=utf-8" xpath "string(//h2)" == "Error" xpath "string(//h3)" == "Not Found"

现在文件里有两个entry:每个 entry 由一个请求和一个期望响应描述(expected response description)组成。两个请求按文件顺序依次执行,每个响应都可以被独立测试。

响应描述是可选的。文件也可以只写请求:

GET http://localhost:3000 GET http://localhost:3000/not-found

但这种写法几乎等于不做测试。不过这类文件在另一种场景下很有用:当你把 Hurl 当作取数工具(用纯文本发请求、拿回响应内容)而非测试工具时。

运行验证:

$ hurl --test basic.hurl basic.hurl: Success (2 request(s) in 21 ms) -------------------------------------------------------------------------------- Executed files: 1 Executed requests: 2 (90.9/s) Succeeded files: 1 (100.0%) Failed files: 0 (0.0%) Duration: 22 ms

测试仍然通过,此时已经是两个请求按序执行。

--test模式的执行语义

从 手册 对--test选项的说明可以确认几个影响链式测试行为的细节:

  • --test激活测试模式:HTTP 响应体不再打印到标准输出,而是逐个 Hurl 文件报告进度,全部跑完后输出一段文本汇总(即上面看到的Executed files / Succeeded files表格);
  • 测试模式下,多个文件默认并行执行(每个文件一个工作线程、互不共享状态);要按文件顺序串行执行,加--jobs 1
  • 该选项对应环境变量HURL_TEST,属于仅命令行可用的选项。

注意区分两个层面:文件与文件之间在测试模式下可并行,而单个文件内部的多个请求始终是顺序执行的——后一个请求可以用前一个请求捕获到的值(教程后续章节的 Captures 会用到这一特性)。

测试 REST API:JSONPath 断言

到目前为止测试的是两个 HTML 端点。下面测试 REST API:示例网站在http://localhost:3000/api/health暴露了一个 health 资源。

先在 shell 里直接验证。Hurl 作为经典 CLI 应用,可以像很多 Unix 工具一样从标准输入读取请求(用-或管道),并把结果管道给其他工具(如jq):

$ echo 'GET http://localhost:3000/api/health' | hurl {"status":"RUNNING","healthy":true,"operationId":6212054377712155,"reportedDate":"2023-07-21T16:11:24.053Z"} $ echo 'GET http://localhost:3000/api/health' | hurl | jq { "status": "RUNNING", "healthy": true, "operationId": 8629192252836205, "reportedDate": "2023-08-04T11:04:52.516Z" }

这意味着 Hurl 既能写文件化、可版本化的回归测试,也能当交互式探针使用——排查问题时先echo ... | hurl看一眼真实响应,再把请求固化进.hurl文件加断言。

把 health API 加进basic.hurl,用JSONPath 断言

# Check our health API: GET http://localhost:3000/api/health HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$.status" == "RUNNING" jsonpath "$.healthy" == true jsonpath "$.operationId" exists

JSONPath 断言与 XPath 断言结构相同:一个查询(JSONPath 表达式,用于检查 JSON 对象)+ 一个谓词。与 XPath 断言一样,JSONPath 谓词的值是带类型的:字符串、布尔、数字、日期和集合都受支持(见 JSONPath 断言)。

从源码结构看,请求执行结果与 JSONPath 断言的求值/比较逻辑集中在运行器中,例如 packages/hurl/src/runner/result.rs 里包含JsonPathAssert相关处理;断言语义本身的规范则以 docs/spec 下的说明为准。

从浏览器 XHR 到 API 断言

教程的第二个例子来自 Movies Box 网站的用户功能:可以按演员、导演、上映年份搜索电影,搜索页在http://localhost:3000/search。输入 "1982" 就能看到 1982 年上映的电影。搜索页通过浏览器的 XHR 请求后端 REST APIhttp://localhost:3000/api/search获取结果——用浏览器开发者工具可以看到这条请求:

把这个"页面上看到的请求"直接变成 Hurl 断言,就是对 API 做回归测试的典型路径:

# Check search API: GET http://localhost:3000/api/search?q=1982&sort=name HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$" count == 5 jsonpath "$[0].name" == "Blade Runner" jsonpath "$[0].director" == "Ridley Scott" jsonpath "$[0].release_date" == "1982-06-25"

教程提示:Movies Box 为了教学方便直接内置了 mock 数据,生产应用不应这样做。更稳妥的做法是通过环境变量为应用提供 "integration/debug" 模式(或 mock 数据库),让断言在已知数据上运行。

这里用到了count过滤器:jsonpath "$" count == 5断言整个结果数组有 5 个元素(count统计集合元素个数,见 filters 文档)。

使用 [Query] 参数段代替 URL 拼接

上面的 URL 直接写了查询参数?q=1982&sort=name。Hurl 也支持把它们放进请求的[Query]段:

# Check search API: GET http://localhost:3000/api/search [Query] q: 1982 sort: name HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$" count == 5 jsonpath "$[0].name" == "Blade Runner" jsonpath "$[0].director" == "Ridley Scott" jsonpath "$[0].release_date" == "1982-06-25"

关于[Query]段,请求语法文档 给出了几条需要记住的规则:

  • 查询参数由字段: 值组成,段以[Query]开头;
  • 参数段中的值不做 URL 编码(这与直接写在 URL 里的参数不同,URL 里需要手工转义,如Install%20Linux),因此含空格、特殊字符的值用参数段写起来更直观;
  • 如果 URL 和[Query]同时存在参数,最终请求会合并两者一起发送,而不是相互覆盖;
  • 各请求段([Cookies][Query][Form]等)之间无固定顺序,可以任意混合书写,但请求体必须位于请求的最后。

谓词与过滤器:从"等于"到"格式校验"

到这里只测试了"服务端返回值等于期望值"。Hurl 的断言体系由三层组成:查询(xpath/jsonpath/header/cookie 等)、过滤器(对查询结果做变换)、谓词(对变换后的值做判断)。

谓词(predicates)

==exists外,断言文档的谓词表 定义了更多类型化谓词:

谓词语义示例
startsWith查询结果以谓词值开头(字符串或二进制)jsonpath "$.movie" startsWith "The"
endsWith查询结果以谓词值结尾(字符串或二进制)jsonpath "$.movie" endsWith "Back"
contains集合包含该值;或字符串/二进制包含该子串jsonpath "$.numbers" contains 42
matches查询字符串部分匹配正则模式jsonpath "$.release" matches /\d{4}/

任何谓词都可以通过前缀not取反(如not contains)。类型约束要注意:startsWith/contains只能作用于字符串和字节,matches只能作用于字符串;查询结果是数字时不能套用字符串谓词。

release_date的断言从精确相等放宽为前缀匹配:

# Check search API: GET http://localhost:3000/api/search [Query] q: 1982 sort: name HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$" count == 5 jsonpath "$[0].name" == "Blade Runner" jsonpath "$[0].director" == "Ridley Scott" jsonpath "$[0].release_date" startsWith "1982"

startsWith "1982"只验证年份开头。如果想既宽松又严格——只关心年份、但要求日期整体符合YYYY-MM-DD格式——就引入过滤器。

regex 过滤器:从查询值中提取片段

filters 文档 中的regex过滤器:提取正则的捕获组内容,模式至少需要一组捕获组;正则语法遵循 Rustregexcrate(Hurl 用 Rust 编写,正则实现与regexcrate 的语法一致)。

最终写法:

# Check search API: GET http://localhost:3000/api/search [Query] q: 1982 sort: name HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$" count == 5 jsonpath "$[0].name" == "Blade Runner" jsonpath "$[0].director" == "Ridley Scott" jsonpath "$[0].release_date" regex /(\d{4})-\d{2}-\d{2}/ == "1982"

逐段拆解这条断言:

  • jsonpath "$[0].release_date"—— JSONPath 查询,从响应中提取第一部电影的上映日期;
  • regex /(\d{4})-\d{2}-\d{2}/—— regex 过滤器,正则写法与 JavaScript 一样用/.../包裹;其中捕获组(\d{4})把 4 位年份从完整日期中提取出来;
  • == "1982"—— 谓词,断言提取出的年份等于期望值。

这条断言同时完成了两件事:格式校验(不匹配YYYY-MM-DD就失败)+ 值校验(年份必须是 1982)。过滤器可以级联组合来细化查询值,这是 Hurl 断言表达力的核心来源——前面已经用过一次countjsonpath "$" count == 5),这里是第二次使用过滤器。

完整的四请求测试文件与运行结果

最终,包含四个 HTTP 请求的basic.hurl完整形态:

# Checking our home page: GET http://localhost:3000 HTTP 200 [Asserts] xpath "string(//head/title)" == "Movies Box" xpath "//h3" count == 2 xpath "string((//h3)[1])" contains "Popular" xpath "string((//h3)[2])" contains "Featured Today" # Testing HTTP response headers: header "Content-Type" == "text/html; charset=utf-8" cookie "x-session-id" exists cookie "x-session-id[HttpOnly]" exists # Check that we have a 404 response for broken links: GET http://localhost:3000/not-found HTTP 404 [Asserts] header "Content-Type" == "text/html; charset=utf-8" xpath "string(//h2)" == "Error" xpath "string(//h3)" == "Not Found" # Check our health API: GET http://localhost:3000/api/health HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$.status" == "RUNNING" jsonpath "$.healthy" == true jsonpath "$.operationId" exists # Check search API: GET http://localhost:3000/api/search [Query] q: 1982 sort: name HTTP 200 [Asserts] header "Content-Type" == "application/json; charset=utf-8" jsonpath "$" count == 5 jsonpath "$[0].name" == "Blade Runner" jsonpath "$[0].director" == "Ridley Scott" jsonpath "$[0].release_date" regex /(\d{4})-\d{2}-\d{2}/ == "1982"

运行:

$ hurl --test basic.hurl basic.hurl: Success (4 request(s) in 21 ms) -------------------------------------------------------------------------------- Executed files: 1 Executed requests: 4 (181.8/s) Succeeded files: 1 (100.0%) Failed files: 0 (0.0%) Duration: 22 ms

每个请求的所有断言全部成功,Failed files为 0。

小结

  • Hurl 文件由多个entry组成,entry 之间用空行分隔,按顺序执行;同一文件里可以混合 HTML 页面测试与 REST API 测试,每个响应独立断言;
  • 响应描述(HTTP 200[Asserts]等)是可选的,纯请求文件适合把 Hurl 当取数 CLI 用(可配echo ... | hurl | jq管道);
  • JSONPath 断言 = 查询 + 谓词,谓词值带类型;startsWith/endsWith/contains/matches等谓词都可用not取反;
  • 查询参数优先用[Query]段书写(值不做 URL 编码,与 URL 内参数合并发送);
  • 过滤器(countregex等)可与查询、谓词自由组合,实现"提取 + 变换 + 判断"三级断言。

教程的收尾建议同样值得记住:随着 Hurl 文件增长,不要吝啬写注释——Hurl 文件本身就会成为应用一份"可执行的文档"(executable documentation),它既是测试,也是随时可运行的接口说明。下一章 调试技巧 会讲解断言失败时如何快速定位问题。

【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:30:18

C#与C语言核心差异与应用场景全解析

1. 项目概述:C#与C语言的定位差异在编程语言的世界里,C#和C就像两个不同时代的建筑大师。C语言诞生于1972年,是系统编程领域的基石语言,直接影响操作系统内核、嵌入式系统等底层开发。而C#作为2000年问世的现代语言,更…

作者头像 李华
网站建设 2026/9/13 8:30:06

Lithe-IDEA:面向Spring Boot的轻量级Java开发环境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 8:28:42

分部积分法怎么选u?公式法与表格法速成技巧全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 8:28:04

HCIA综合实验完整实战:从VLAN到NAT的企业网络配置与排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华