接口自动化测试实战:从 HTTP 基础到 JMeter + Newman 全链路覆盖
环境说明
节点 公网 IP 私网 IP 规格 用途 ecs-916d-0001 121.36.51.148 192.168.0.178 2C4G Ubuntu 24.04 被测系统(悦购图书商城 REST API) ecs-916d-0002 119.3.175.121 192.168.0.246 2C4G Ubuntu 24.04 Newman 命令行执行节点 ecs-916d-0003 1.92.93.183 192.168.0.66 2C4G Ubuntu 24.04 JMeter 5.6.3 测试节点 ecs-916d-0004 120.46.82.41 192.168.0.222 2C4G Ubuntu 24.04 Ant 报告生成节点
一、接口测试基础概念
1.1 什么是接口测试
接口(API)是系统组件之间通信的契约,规定了"我提供什么数据、你需要传什么参数、我返回什么格式"。接口测试就是绕开 UI,直接对这层契约进行验证。
传统 UI 测试路径:
测试人员 → 浏览器/App → UI 层 → 业务层 → 数据层
接口测试路径:
测试人员 → HTTP 请求 → 业务层 → 数据层
(绕过 UI,直达后端)
接口测试的核心价值:
- 发现更早:UI 还没做好时接口就可以测
- 覆盖更深:能构造 UI 无法触发的异常入参
- 运行更快:无渲染开销,秒级执行
- 定位更准:响应报文直接暴露后端逻辑问题
1.2 接口测试的分类
| 分类维度 | 类型 | 说明 |
|---|---|---|
| 协议 | HTTP/HTTPS | 最常见,Web 服务主流协议 |
| 协议 | RPC / gRPC | 微服务内部调用,高性能 |
| 协议 | WebSocket | 长连接,实时推送场景 |
| 协议 | GraphQL | 前端自定义查询结构 |
| 场景 | 单接口测试 | 验证单个 API 的功能正确性 |
| 场景 | 场景链路测试 | 多接口依赖串联,如登录→下单→查询 |
| 场景 | 边界/异常测试 | 空参数、超长字段、越权访问等 |
1.3 HTTP 协议核心要素
请求报文结构
POST /api/user/login HTTP/1.1 ← 请求行(方法 路径 版本)
Host: 121.36.51.148:8080 ← 请求头(Headers)
Content-Type: application/json
Authorization: Bearer abc123token
← 空行(分隔头和体)
{"email":"test@example.com","password":"123456"} ← 请求体(Body)
常用 HTTP 方法对照
| 方法 | 语义 | 幂等 | 典型场景 |
|---|---|---|---|
| GET | 查询资源 | ✅ | 获取图书列表、详情 |
| POST | 创建资源 | ❌ | 用户登录、提交订单 |
| PUT | 全量更新 | ✅ | 更新用户信息 |
| PATCH | 部分更新 | ✅ | 修改单个字段 |
| DELETE | 删除资源 | ✅ | 删除记录 |
响应状态码速查
| 范围 | 含义 | 常见码 |
|---|---|---|
| 2xx | 成功 | 200 OK、201 Created、204 No Content |
| 3xx | 重定向 | 301 永久、302 临时 |
| 4xx | 客户端错误 | 400 参数错误、401 未授权、403 禁止、404 不存在 |
| 5xx | 服务端错误 | 500 内部错误、502 网关错误、503 服务不可用 |
1.4 接口测试用例设计思路
接口测试用例需覆盖以下维度:
- 正常场景:按文档预期的正确入参,验证返回 2xx 和业务数据正确
- 边界值:最大/最小长度、最大/最小数值、边界时间戳
- 异常参数:空值、null、错误类型(数字传字符串)
- 权限验证:无 Token、过期 Token、越权访问
- 业务规则:库存不足下单、重复注册、金额精度
二、被测系统:悦购图书商城 REST API
2.1 系统部署(itest-01: 121.36.51.148)
在 ecs-916d-0001 上部署 Flask REST API 作为被测系统:
bash
# 安装依赖
apt-get install -y python3 python3-pip
pip3 install 'flask>=3.0' --break-system-packages --ignore-installed
# 查看安装结果
root@ecs-916d-0001:~# python3 -c "import flask; print(flask.__version__)"
3.1.3
API 服务提供以下接口(运行在 8080 端口):
| 方法 | 路径 | 说明 | 认证 |
|---|---|---|---|
| GET | /api/health | 健康检查 | 无需 |
| GET | /api/books | 获取图书列表(支持分页/关键词搜索) | 无需 |
| GET | /api/books/:id | 获取单本图书详情 | 无需 |
| POST | /api/user/login | 用户登录,返回 Token | 无需 |
| POST | /api/order/create | 创建订单 | Bearer Token |
| GET | /api/order/list | 查询订单列表 | Bearer Token |
bash
# 启动服务
nohup python3 /opt/bookstore_api.py > /var/log/bookstore.log 2>&1 &
# 验证服务状态
root@ecs-916d-0001:~# curl -s http://localhost:8080/api/health
{"service":"悦购图书商城 API","status":"ok","version":"1.0.0"}
2.2 接口文档分析示例
以「用户登录」接口为例,拆解接口文档:
接口名称:用户登录
请求方法:POST
请求路径:/api/user/login
请求头:Content-Type: application/json
请求参数(Body JSON):
email string 必填 用户邮箱,格式:xxx@xxx.xxx
password string 必填 密码,6-20位
响应示例(成功):
HTTP 200
{"code":200,"message":"登录成功","data":{"token":"abc123token","name":"测试用户"}}
响应示例(失败):
HTTP 400 - 参数缺失 {"code":400,"message":"邮箱不能为空"}
HTTP 401 - 密码错误 {"code":401,"message":"用户名或密码错误"}
三、curl 原始接口测试
curl 是最基础的接口测试工具,掌握它能帮助快速验证接口可达性,也是理解 HTTP 请求结构的最佳方式。
3.1 GET 请求测试
bash
# 健康检查 - 验证服务可用性
root@ecs-916d-0001:~# curl -s -w "\nHTTP_CODE:%{http_code} TIME:%{time_total}s" \
http://localhost:8080/api/health
# 实际输出:
{"service":"悦购图书商城 API","status":"ok","version":"1.0.0"}
HTTP_CODE:200 TIME:0.003124s
bash
# 获取图书列表(分页)
root@ecs-916d-0001:~# curl -s "http://localhost:8080/api/books?page=1&size=10"
# 实际输出:
{
"code": 200,
"data": [
{"author": "Eric Matthes", "id": 1, "price": 89.0, "stock": 100, "title": "Python编程:从入门到实践"},
{"author": "Cay S. Horstmann", "id": 2, "price": 149.0, "stock": 50, "title": "Java核心技术"},
{"author": "Glenford J. Myers", "id": 3, "price": 59.0, "stock": 200, "title": "软件测试的艺术"},
{"author": "David Gourley", "id": 4, "price": 119.0, "stock": 30, "title": "HTTP权威指南"}
],
"page": 1,
"size": 10,
"total": 4
}
bash
# 关键词搜索
root@ecs-916d-0001:~# curl -s "http://localhost:8080/api/books?keyword=Python"
# 实际输出:
{
"code": 200,
"data": [{"author": "Eric Matthes", "id": 1, "price": 89.0, "stock": 100, "title": "Python编程:从入门到实践"}],
"page": 1,
"size": 10,
"total": 1
}
bash
# 异常场景:访问不存在的图书(预期 404)
root@ecs-916d-0001:~# curl -s -w "\nHTTP_CODE:%{http_code}" http://localhost:8080/api/books/9999
# 实际输出:
{"code":404,"message":"图书不存在"}
HTTP_CODE:404
3.2 POST 请求测试
bash
# 用户登录(正常场景)
root@ecs-916d-0001:~# curl -s -X POST http://localhost:8080/api/user/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"123456"}'
# 实际输出:
{
"code": 200,
"data": {
"name": "测试用户",
"token": "abc123token"
},
"message": "登录成功"
}
bash
# 密码错误(异常场景,预期 401)
root@ecs-916d-0001:~# curl -s -w "\nHTTP_CODE:%{http_code}" \
-X POST http://localhost:8080/api/user/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"wrong"}'
# 实际输出:
{"code":401,"message":"用户名或密码错误"}
HTTP_CODE:401
bash
# 带 Token 认证:创建订单
root@ecs-916d-0001:~# curl -s -X POST http://localhost:8080/api/order/create \
-H "Content-Type: application/json" \
-H "Authorization: Bearer abc123token" \
-d '{"book_id":1,"quantity":2}'
# 实际输出:
{
"code": 200,
"data": {
"book_id": 1,
"order_id": 1001,
"quantity": 2,
"title": "Python编程:从入门到实践",
"total": 178.0
},
"message": "下单成功"
}
bash
# 未携带 Token(预期 401)
root@ecs-916d-0001:~# curl -s -w "\nHTTP_CODE:%{http_code}" \
-X POST http://localhost:8080/api/order/create \
-H "Content-Type: application/json" \
-d '{"book_id":1}'
# 实际输出:
{"code":401,"message":"未授权,请先登录"}
HTTP_CODE:401
四、使用 Postman/Newman 进行接口测试
4.1 Postman 简介与安装
Postman 是最流行的 API 测试工具,提供图形界面,支持请求管理、环境变量、断言脚本、Mock Server 等功能。
下载安装(Windows/Mac/Linux):
- 官网:https://www.postman.com/downloads/
- 选择对应系统版本,解压即可运行
核心界面区域:
┌─────────────────────────────────────────────────────────┐
│ Collection 面板 │ 请求编辑区 │
│ ┌─────────────┐ │ ┌──────────────────────────────┐ │
│ │ 悦购API测试 │ │ │ GET ▼ http://xxx/api/books │ │
│ │ ├ 健康检查 │ │ ├──────────────────────────────┤ │
│ │ ├ 图书列表 │ │ │ Params | Headers | Body | ... │ │
│ │ ├ 用户登录 │ │ ├──────────────────────────────┤ │
│ │ └ 创建订单 │ │ │ Tests (断言脚本) │ │
│ └─────────────┘ │ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
4.2 发送 GET 请求
在 Postman 中配置图书列表请求:
- Method: GET
- URL :
http://121.36.51.148:8080/api/books - Params 标签页添加查询参数:
page=1size=10
4.3 发送 POST 请求
用户登录请求配置:
-
Method: POST
-
URL :
http://121.36.51.148:8080/api/user/login -
Headers :
Content-Type: application/json -
Body → raw → JSON:
json{ "email": "test@example.com", "password": "123456" }
4.4 Tests 中添加断言
在 Tests 标签页中用 JavaScript 编写断言:
javascript
// 断言1:验证状态码为 200
pm.test("状态码为200", function () {
pm.response.to.have.status(200);
});
// 断言2:验证响应体中 code 字段为 200
pm.test("业务code为200", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.code).to.eql(200);
});
// 断言3:验证返回的 token 存在
pm.test("Token不为空", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.data.token).to.exist;
});
// 断言4:响应时间小于 500ms
pm.test("响应时间小于500ms", function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});
// 断言5:提取 token 存入环境变量(供后续接口使用)
pm.environment.set("token", pm.response.json().data.token);
4.5 环境变量与接口依赖
通过环境变量实现接口间的数据传递(登录 → 下单):
javascript
// 在"用户登录"请求的 Tests 中提取 token
pm.environment.set("token", pm.response.json().data.token);
// 在"创建订单"请求的 Headers 中使用变量
// Authorization: Bearer {{token}}
环境变量设置步骤:
- 右上角 → Environments → Add
- 新建环境
bookstore-dev - 添加变量:
base_url=http://121.36.51.148:8080
4.6 Collection 管理与导出
将所有接口组织到一个 Collection 中便于统一管理:
悦购图书商城 API 接口测试集合
├── 第一节:基础查询
│ ├── GET 健康检查
│ ├── GET 图书列表(分页)
│ ├── GET 关键词搜索
│ └── GET 图书详情
├── 第二节:用户认证
│ ├── POST 登录(正常)
│ ├── POST 登录(密码错误)
│ └── POST 登录(邮箱为空)
└── 第三节:订单业务
├── POST 创建订单(已认证)
├── POST 创建订单(未认证)
└── GET 查询订单列表
导出 Collection:Collection → ... → Export → Collection v2.1
4.7 Newman 命令行执行(itest-02: 119.3.175.121)
Newman 是 Postman 的命令行运行器,实现无界面的 CI 集成。
安装 Newman(ecs-916d-0002 节点):
bash
# 安装 Node.js 和 npm
root@ecs-916d-0002:~# apt-get install -y nodejs npm openjdk-17-jdk
# 安装 Newman 及 HTML 报告插件
root@ecs-916d-0002:~# npm install -g newman newman-reporter-htmlextra
# 验证安装
root@ecs-916d-0002:~# newman --version
6.2.2
执行 Collection:
bash
root@ecs-916d-0002:~# newman run /opt/postman-tests/bookstore_collection.json \
--reporters cli
真实执行输出:
newman
悦购图书商城 API 接口测试集合
→ 健康检查
GET http://121.36.51.148:8080/api/health [200 OK, 252B, 28ms]
✓ 状态码为200
✓ 服务状态正常
→ 获取图书列表
GET 121.36.51.148:8080/api/books?page=1&size=10 [200 OK, 661B, 10ms]
✓ 状态码为200
✓ 返回图书列表
✓ 响应时间小于500ms
→ 关键词搜索图书
GET 121.36.51.148:8080/api/books?keyword=Python [200 OK, 346B, 11ms]
✓ 搜索结果包含Python图书
→ 获取单本图书详情
GET http://121.36.51.148:8080/api/books/1 [200 OK, 315B, 7ms]
✓ 返回图书详情
✓ 图书包含必要字段
→ 获取不存在的图书(异常场景)
GET http://121.36.51.148:8080/api/books/9999 [404 NOT FOUND, 228B, 8ms]
✓ 返回404状态码
✓ 返回错误信息
→ 用户登录(正常场景)
POST http://121.36.51.148:8080/api/user/login [200 OK, 281B, 9ms]
✓ 登录成功
→ 用户登录(密码错误)
POST http://121.36.51.148:8080/api/user/login [401 UNAUTHORIZED, 249B, 11ms]
✓ 密码错误返回401
→ 用户登录(邮箱为空)
POST http://121.36.51.148:8080/api/user/login [400 BAD REQUEST, 236B, 8ms]
✓ 邮箱为空返回400
→ 创建订单(已登录)
POST http://121.36.51.148:8080/api/order/create [200 OK, 351B, 7ms]
✓ 创建订单成功
✓ 订单金额正确
→ 创建订单(未授权)
POST http://121.36.51.148:8080/api/order/create [401 UNAUTHORIZED, 249B, 7ms]
✓ 未授权返回401
┌─────────────────────────┬──────────────────┬─────────────────┐
│ │ executed │ failed │
├─────────────────────────┼──────────────────┼─────────────────┤
│ iterations │ 1 │ 0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│ requests │ 10 │ 0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│ test-scripts │ 10 │ 0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│ prerequest-scripts │ 0 │ 0 │
├─────────────────────────┼──────────────────┼─────────────────┤
│ assertions │ 16 │ 0 │
├─────────────────────────┴──────────────────┴─────────────────┤
│ total run duration: 251ms │
├──────────────────────────────────────────────────────────────┤
│ total data received: 1.48kB (approx) │
├──────────────────────────────────────────────────────────────┤
│ average response time: 10ms [min: 7ms, max: 28ms, s.d.: 5ms] │
└──────────────────────────────────────────────────────────────┘
结果 :10 个请求,16 个断言,全部通过(0 失败),平均响应时间 10ms。
生成 HTML 报告:
bash
root@ecs-916d-0002:~# newman run /opt/postman-tests/bookstore_collection.json \
--reporters cli,htmlextra \
--reporter-htmlextra-export /opt/postman-tests/report.html
五、使用 JMeter 进行接口测试
5.1 JMeter 简介
Apache JMeter 是一款纯 Java 的开源负载测试工具,既可做接口功能测试,也可做性能压力测试。相比 Postman,JMeter 更适合:
-
需要多线程并发模拟的场景
-
需要命令行无 GUI 执行的 CI/CD 集成
-
需要生成专业 HTML 报告
-
测试规模较大(数百个接口、数千并发)
JMeter 工程结构(元件树):
测试计划 (Test Plan)
└── 线程组 (Thread Group) ← 并发用户数配置
├── HTTP 请求默认值 (Config) ← 公共配置(域名、端口)
├── HTTP 信息头管理器 (Header Manager) ← 公共 Headers
├── 取样器 (Sampler) ← 具体的接口请求
│ ├── 断言 (Assertion) ← 验证响应
│ ├── 提取器 (Extractor) ← 提取变量
│ └── 监听器 (Listener) ← 查看结果
└── ...
5.2 JMeter 安装(itest-03: 1.92.93.183)
bash
# 安装 JDK 17(JMeter 5.6+ 要求 Java 8+)
root@ecs-916d-0003:~# apt-get install -y openjdk-17-jdk
root@ecs-916d-0003:~# java -version
openjdk version "17.0.19" 2026-04-21
OpenJDK Runtime Environment (build 17.0.19+10-1-24.04.2-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.19+10-1-24.04.2-Ubuntu, mixed mode, sharing)
# 下载 JMeter(阿里云镜像加速)
root@ecs-916d-0003:~# wget -q \
https://mirrors.aliyun.com/apache/jmeter/binaries/apache-jmeter-5.6.3.tgz \
-O /tmp/jmeter.tgz
# 解压并配置
root@ecs-916d-0003:~# tar -xzf /tmp/jmeter.tgz -C /opt/
root@ecs-916d-0003:~# ln -sf /opt/apache-jmeter-5.6.3 /opt/jmeter
root@ecs-916d-0003:~# echo 'export PATH=$PATH:/opt/jmeter/bin' >> /etc/profile
root@ecs-916d-0003:~# source /etc/profile
# 验证安装
root@ecs-916d-0003:~# jmeter --version 2>&1 | grep -i "5\."
/_/ \_\_| /_/ \_\____|_| |_|_____| \___/|_| |_|_____| |_| |_____|_| \_\ 5.6.3
5.3 核心组件详解
线程组(Thread Group)
线程组控制并发用户数量,是 JMeter 测试的基础:
线程组参数:
线程数(Threads) = 5 ← 模拟 5 个并发用户
Ramp-Up 时间(秒) = 2 ← 2 秒内依次启动全部线程
循环次数(Loop Count) = 3 ← 每个线程执行 3 次
总请求数 = 5×3×4 = 60 个请求(4个取样器)
取样器(HTTP Request Sampler)
xml
<!-- GET 请求示例 -->
<HTTPSamplerProxy testname="GET 图书列表">
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<!-- 查询参数 -->
<elementProp name="HTTPsampler.Arguments">
<elementProp name="page"><stringProp name="Argument.value">1</stringProp></elementProp>
<elementProp name="size"><stringProp name="Argument.value">10</stringProp></elementProp>
</elementProp>
</HTTPSamplerProxy>
<!-- POST 请求示例(Raw Body) -->
<HTTPSamplerProxy testname="POST 用户登录">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<stringProp name="Argument.value">{"email":"test@example.com","password":"123456"}</stringProp>
<stringProp name="HTTPSampler.path">/api/user/login</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
</HTTPSamplerProxy>
响应断言
xml
<!-- 断言 HTTP 状态码 -->
<ResponseAssertion testname="断言-状态码200">
<stringProp>200</stringProp>
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
<intProp name="Assertion.test_type">8</intProp> <!-- 8=equals -->
</ResponseAssertion>
JSON 提取器(接口依赖)
xml
<!-- 从登录响应中提取 token,供后续请求使用 -->
<JSONPostProcessor testname="JSON提取器-Token">
<stringProp name="JSONPostProcessor.referenceNames">login_token</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$.data.token</stringProp>
</JSONPostProcessor>
<!-- 在创建订单请求的 Header Manager 中引用变量 -->
<!-- Authorization: Bearer ${login_token} -->
JSON 断言
xml
<!-- 验证响应体中 $.code == 200 -->
<JSONPathAssertion testname="JSON断言-code">
<stringProp name="JSON_PATH">$.code</stringProp>
<stringProp name="EXPECTED_VALUE">200</stringProp>
<boolProp name="JSONVALIDATION">true</boolProp>
</JSONPathAssertion>
5.4 JMX 脚本结构(完整示例)
xml
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" jmeter="5.6.3">
<hashTree>
<TestPlan testname="悦购图书商城接口测试">
<!-- 全局用户自定义变量 -->
<elementProp name="TestPlan.user_defined_variables">
<elementProp name="BASE_URL"><stringProp name="Argument.value">121.36.51.148</stringProp></elementProp>
<elementProp name="PORT"><stringProp name="Argument.value">8080</stringProp></elementProp>
<elementProp name="TOKEN"><stringProp name="Argument.value">abc123token</stringProp></elementProp>
</elementProp>
</TestPlan>
<hashTree>
<!-- 线程组:5线程 × 3次循环 -->
<ThreadGroup testname="接口测试线程组">
<stringProp name="ThreadGroup.num_threads">5</stringProp>
<stringProp name="ThreadGroup.ramp_time">2</stringProp>
<stringProp name="LoopController.loops">3</stringProp>
</ThreadGroup>
<hashTree>
<!-- HTTP 请求默认值(域名/端口复用) -->
<ConfigTestElement testname="HTTP请求默认值">
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
<stringProp name="HTTPSampler.port">${PORT}</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
</ConfigTestElement>
<!-- 取样器1:GET 健康检查(含响应断言) -->
<!-- 取样器2:GET 图书列表(含 JSON 断言) -->
<!-- 取样器3:POST 用户登录(含 JSON 提取器) -->
<!-- 取样器4:POST 创建订单(含 Authorization Header) -->
<!-- 结果收集器 -->
<ResultCollector testname="汇总报告">
<stringProp name="filename">/opt/jmeter-results/bookstore_result.jtl</stringProp>
</ResultCollector>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
5.5 命令行执行与结果分析
命令行参数说明:
bash
/opt/jmeter/bin/jmeter \
-n # non-GUI 模式(无界面,适合服务器/CI)
-t <test.jmx> # 指定测试脚本
-l <result.jtl> # 指定结果输出文件(JTL 格式)
-e # 执行完成后生成 HTML 报告
-o <report-dir> # HTML 报告输出目录(需为空目录)
实际执行过程:
bash
root@ecs-916d-0003:~# mkdir -p /opt/jmeter-tests /opt/jmeter-results
root@ecs-916d-0003:~# /opt/jmeter/bin/jmeter \
-n -t /opt/jmeter-tests/bookstore_test.jmx \
-l /opt/jmeter-results/bookstore_result.jtl \
-e -o /opt/jmeter-results/html-report 2>&1 | grep -v WARN
Creating summariser <summary>
Created the tree successfully using /opt/jmeter-tests/bookstore_test.jmx
Starting standalone test @ 2026 Jun 30 17:43:14 CST (1782812594041)
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary = 60 in 00:00:02 = 34.5/s Avg: 8 Min: 6 Max: 25 Err: 0 (0.00%)
Tidying up ... @ 2026 Jun 30 17:43:15 CST (1782812595845)
... end of run
结果解读:
summary = 60 in 00:00:02 = 34.5/s Avg: 8 Min: 6 Max: 25 Err: 0 (0.00%)
│ │ │ │ │ │ └── 错误率:0% ✅
│ │ │ │ │ └── 最大响应时间:25ms
│ │ │ │ └── 最小响应时间:6ms
│ │ │ └── 平均响应时间:8ms
│ │ └── 吞吐量:34.5 请求/秒
│ └── 执行耗时:2秒
└── 总请求数:60(5线程 × 3次循环 × 4个接口)
各接口性能汇总(从 JTL 文件统计):
接口名称 样本数 通过数 平均(ms) 最小(ms) 最大(ms)
GET 健康检查 15 15 9.9 7 25
GET 图书列表 15 15 8.5 6 11
POST 用户登录 15 15 8.1 6 10
POST 创建订单 15 15 8.9 6 11
全部 60 个请求通过,错误率 0.00%,所有接口平均响应时间均在 10ms 以内(内网环境)。
5.6 常见 JMeter 组件用法
CSV 数据文件参数化
bash
# 创建测试数据文件
cat > /opt/jmeter-tests/books.csv << EOF
book_id,quantity
1,1
2,2
3,3
4,1
EOF
在 JMX 中配置 CSV Data Set Config:
xml
<CSVDataSet testname="CSV数据文件配置">
<stringProp name="filename">/opt/jmeter-tests/books.csv</stringProp>
<stringProp name="variableNames">book_id,quantity</stringProp>
<stringProp name="delimiter">,</stringProp>
<boolProp name="recycle">true</boolProp>
</CSVDataSet>
<!-- 请求中使用 ${book_id} 和 ${quantity} 变量 -->
逻辑控制器
循环控制器(Loop Controller):重复执行子节点 N 次
If 控制器(If Controller):条件判断(如 ${responseCode} == 200)
ForEach 控制器:遍历变量集合
事务控制器(Transaction Controller):将多个请求组合为一个事务统计
函数助手
常用内置函数(在 JMeter 菜单 Tools → Function Helper Dialog 中调试):
${__time(yyyy-MM-dd HH:mm:ss)} # 当前时间
${__Random(1,100)} # 1~100 随机数
${__UUID()} # 生成 UUID
${__MD5(${password})} # MD5 加密
${__CSVRead(file.csv,0)} # 读取 CSV 第0列
六、Fiddler 抓包分析
6.1 Fiddler 工作原理
Fiddler 作为本地 HTTP 代理(默认端口 8888),拦截并记录所有流经的 HTTP/HTTPS 流量:
┌──────────────┐
浏览器/App ──▶ Fiddler ──▶ 目标服务器
│ (localhost │
│ :8888) │
└──────────────┘
↕ 记录
会话列表面板
6.2 HTTPS 抓包配置
1. 菜单 Tools → Options → HTTPS
2. 勾选 "Decrypt HTTPS traffic"
3. 点击 "Actions" → Trust Root Certificate
4. 在浏览器或系统证书管理中导入 Fiddler 根证书
踩坑记录:Android 7.0+ 默认不信任用户证书,需要 root 设备或使用 Android 模拟器。
6.3 手机端抓包
1. 确保手机与电脑在同一局域网
2. Fiddler: Tools → Options → Connections → 勾选 "Allow remote computers to connect"
3. 手机 WiFi 高级设置 → 手动代理 → 填入电脑 IP:8888
4. 手机浏览器访问 http://电脑IP:8888 → 下载安装证书
6.4 Fiddler 主要功能
| 功能 | 操作 | 用途 |
|---|---|---|
| 断点修改请求 | Rules → Automatic Breakpoints → Before Requests | 修改请求参数后再发出 |
| 断点修改响应 | Rules → Automatic Breakpoints → After Responses | 模拟不同响应场景 |
| Mock 功能 | AutoResponder 标签页 | 伪造接口响应,脱离后端测试前端 |
| 数据过滤 | Filters 标签页 | 只显示特定域名/状态码的请求 |
| 弱网测试 | Rules → Customize Rules → m_SimulateModem | 模拟 2G/3G 网络延迟 |
| FiddlerScript | Rules → Customize Rules | 自定义 C# 脚本批量处理请求 |
七、接口测试用例设计实战
7.1 用例模板
| 字段 | 说明 |
|---|---|
| 用例编号 | TC_模块_序号,如 TC_LOGIN_001 |
| 用例名称 | 简洁描述场景,如「登录成功-正常账号密码」 |
| 接口路径 | POST /api/user/login |
| 请求方法 | POST |
| 请求头 | Content-Type: application/json |
| 请求体 | {"email":"test@example.com","password":"123456"} |
| 前置条件 | 用户已注册 |
| 预期结果 | HTTP 200,code=200,token 不为空 |
| 实际结果 | (执行后填写) |
| 是否通过 | ✅/❌ |
7.2 悦购图书商城接口测试用例设计(完整表)
用户登录模块(TC_LOGIN):
| 编号 | 场景 | 请求 Body | 预期状态码 | 预期结果 |
|---|---|---|---|---|
| TC_LOGIN_001 | 正常登录 | {"email":"test@example.com","password":"123456"} |
200 | code=200,token 非空 |
| TC_LOGIN_002 | 密码错误 | {"email":"test@example.com","password":"wrong"} |
401 | message="用户名或密码错误" |
| TC_LOGIN_003 | 邮箱为空 | {"email":"","password":"123456"} |
400 | message="邮箱不能为空" |
| TC_LOGIN_004 | 密码为空 | {"email":"test@example.com","password":""} |
400 | message="密码不能为空" |
| TC_LOGIN_005 | Body 为空 | {} |
400 | code=400 |
| TC_LOGIN_006 | 邮箱格式错误 | {"email":"notanemail","password":"123456"} |
400/401 | 返回错误提示 |
图书查询模块(TC_BOOK):
| 编号 | 场景 | 请求参数 | 预期状态码 | 预期结果 |
|---|---|---|---|---|
| TC_BOOK_001 | 获取全部图书 | page=1&size=10 | 200 | data 数组非空 |
| TC_BOOK_002 | 关键词搜索 | keyword=Python | 200 | total≥1,结果包含 Python |
| TC_BOOK_003 | 无结果搜索 | keyword=不存在的书 | 200 | total=0,data=\[\] |
| TC_BOOK_004 | 获取图书详情 | /api/books/1 | 200 | id=1,包含所有字段 |
| TC_BOOK_005 | 图书 ID 不存在 | /api/books/9999 | 404 | code=404 |
| TC_BOOK_006 | 分页第2页 | page=2&size=2 | 200 | data 最多2条 |
订单模块(TC_ORDER):
| 编号 | 场景 | Authorization | 请求 Body | 预期结果 |
|---|---|---|---|---|
| TC_ORDER_001 | 创建订单(已登录) | Bearer abc123token | {"book_id":1,"quantity":2} |
200,订单创建成功 |
| TC_ORDER_002 | 创建订单(未登录) | 无 | {"book_id":1,"quantity":1} |
401,未授权 |
| TC_ORDER_003 | 图书ID不存在 | Bearer abc123token | {"book_id":9999} |
404 |
| TC_ORDER_004 | 库存不足 | Bearer abc123token | {"book_id":4,"quantity":999} |
400,库存不足 |
| TC_ORDER_005 | 查询订单列表 | Bearer abc123token | - | 200,返回订单列表 |
八、Newman + Ant 集成持续测试
8.1 Ant 安装(itest-04: 120.46.82.41)
bash
root@ecs-916d-0004:~# apt-get install -y openjdk-17-jdk ant
root@ecs-916d-0004:~# ant -version
Apache Ant(TM) version 1.10.14 compiled on September 25 2023
8.2 Newman 生成 JUnit 报告集成 Ant
bash
# 生成 JUnit XML 格式报告(可被 Jenkins/Ant 解析)
root@ecs-916d-0002:~# newman run /opt/postman-tests/bookstore_collection.json \
--reporters cli,junit \
--reporter-junit-export /opt/postman-tests/junit-report.xml
build.xml 示例(Ant 集成 Newman):
xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="bookstore-api-test" default="run-newman-test" basedir=".">
<property name="collection.file" value="/opt/postman-tests/bookstore_collection.json"/>
<property name="report.dir" value="/opt/postman-tests/reports"/>
<target name="clean">
<delete dir="${report.dir}"/>
<mkdir dir="${report.dir}"/>
</target>
<target name="run-newman-test" depends="clean">
<exec executable="newman" failonerror="true">
<arg value="run"/>
<arg value="${collection.file}"/>
<arg value="--reporters"/>
<arg value="cli,junit,htmlextra"/>
<arg value="--reporter-junit-export"/>
<arg value="${report.dir}/junit-report.xml"/>
<arg value="--reporter-htmlextra-export"/>
<arg value="${report.dir}/html-report.html"/>
</exec>
</target>
<target name="check-result" depends="run-newman-test">
<junitreport todir="${report.dir}">
<fileset dir="${report.dir}">
<include name="junit-report.xml"/>
</fileset>
<report format="frames" todir="${report.dir}/html"/>
</junitreport>
<echo message="测试报告已生成:${report.dir}/html/index.html"/>
</target>
</project>
bash
# 执行 Ant 构建
root@ecs-916d-0004:~# ant run-newman-test
8.3 JMeter 命令行 + 报告生成
bash
# 完整命令:执行测试 + 生成 HTML 报告
root@ecs-916d-0003:~# /opt/jmeter/bin/jmeter \
-n \
-t /opt/jmeter-tests/bookstore_test.jmx \
-l /opt/jmeter-results/result_$(date +%Y%m%d_%H%M%S).jtl \
-e -o /opt/jmeter-results/html-report \
2>&1 | grep -v WARN
# 输出示例:
Creating summariser <summary>
Created the tree successfully using bookstore_test.jmx
Starting standalone test @ 2026 Jun 30 17:43:14 CST
summary = 60 in 00:00:02 = 34.5/s Avg: 8 Min: 6 Max: 25 Err: 0 (0.00%)
... end of run
# 查看 HTML 报告(将报告目录映射到 Web 服务器访问)
root@ecs-916d-0003:~# python3 -m http.server 8090 --directory /opt/jmeter-results/
# 浏览器访问 http://1.92.93.183:8090/html-report/
九、踩坑记录
9.1 JMeter Authorization Header 不生效
现象 :POST 请求需要携带 Authorization Header,但 401 响应一直出现。
原因 :在 JMX 的 HTTPSamplerProxy 标签内嵌套 elementProp name="HTTPSampler.header" 这种写法在 JMeter 5.x 中不被识别。
正确做法 :单独在 hashTree 子节点中添加 HeaderManager 元件:
xml
<HTTPSamplerProxy testname="POST 创建订单">
...
</HTTPSamplerProxy>
<hashTree>
<!-- 正确:独立的 HeaderManager 子元件 -->
<HeaderManager testname="创建订单Header">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Authorization</stringProp>
<stringProp name="Header.value">Bearer ${TOKEN}</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
9.2 Flask pip3 安装报错
现象 :pip3 install flask 报 Cannot uninstall blinker, RECORD file not found
原因:Ubuntu 24.04 的 apt 安装的部分 Python 包与 pip 管理的包冲突。
解决:
bash
pip3 install 'flask>=3.0' --break-system-packages --ignore-installed
9.3 Newman 版本与 Collection 格式
Newman 6.x 要求 Collection 格式为 v2.1(schema.getpostman.com/json/collection/v2.1.0),如果是从老版本 Postman 导出的 v1 格式,运行会报错。
解决 :在 Postman 中重新导出为 v2.1 格式,或手动修改 schema 字段。
9.4 JMeter CSV 参数化中文乱码
现象:CSV 中含有中文,JMeter 读取后在请求中出现乱码。
解决:
- CSV 文件保存为 UTF-8(无 BOM)
CSVDataSet中设置fileEncoding=UTF-8- HTTP 请求默认值中设置
contentEncoding=UTF-8
十、总结
本文通过悦购图书商城 REST API 作为被测系统,完整演示了接口自动化测试的全链路:
接口分析(文档阅读) → curl 验证 → Postman 用例编写
↓
Newman 命令行执行 → HTML/JUnit 报告生成
↓
JMeter JMX 脚本编写 → 命令行并发执行 → HTML 报告分析
↓
Ant 构建集成 → 持续测试
工具选型建议:
| 场景 | 推荐工具 |
|---|---|
| 快速验证单个接口 | curl |
| 接口用例管理 + 手动测试 | Postman |
| CI/CD 中自动化执行 | Newman + Ant |
| 高并发性能 + 大规模接口 | JMeter |
| HTTPS 抓包 + 问题定位 | Fiddler |
测试结果汇总:
| 工具 | 用例数 | 通过 | 失败 | 平均响应 |
|---|---|---|---|---|
| Newman | 10 请求 / 16 断言 | 16 | 0 | 10ms |
| JMeter | 60 个请求 (5线程×3循环×4接口) | 60 | 0 | 8ms |
接口测试是软件质量保障的重要一环,掌握 Postman、Newman、JMeter 三件套,能够覆盖从开发联调、回归测试到性能验证的全生命周期。
环境:华为云 ecs-916d 集群 | Ubuntu 24.04 | Python 3.12.3 / Flask 3.1.3 | JMeter 5.6.3 | Newman 6.2.2 | OpenJDK 17.0.19