给一个待办事项 API 写一套完整的 Postman 自动化测试:登录拿令牌、新建、修改、查询、删除,外加空标题、未登录等异常情况。8 个请求按顺序执行,前一个请求的结果自动传给下一个,点一次 Run 就知道接口有没有坏。
先看清成品
成品是一个 Postman 集合加一个环境。本文已对本地练习 API 实际运行:Newman 6.2.3 和 Postman CLI 1.75.0 都执行了 8 个请求、13 条断言,全部通过。在 Postman 桌面应用里手动操作的部分没有截图验证,菜单名称按官方文档编写。
注意:只对你自己的或者明确允许测试的 API 运行自动化测试,不要对别人的线上服务做压力测试或扫描。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| 练习 API | server.js,登录 + 待办增删改查 |
| 测试集合 | 8 个请求,覆盖正常和异常 |
| 自动传值 | 令牌和新建的 ID 存进集合变量 |
| 结构校验 | 用 JSON Schema 检查返回格式 |
| 命令行运行 | Newman 或 Postman CLI |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Postman 桌面应用 | 免费账号即可 | 官网下载,各计划限制以价格页为准 |
| Node.js | 18 或更高 | 运行练习 API,本文使用 22.23.3 |
| 练习 API | 本文提供的 server.js |
不依赖任何第三方包 |
| Newman | npm i -g newman |
可选,本文使用 6.2.3 |
第一步:怎样启动练习 API?
新建文件夹,放入 package.json(内容 {"type":"module","private":true})和下面的 server.js:
// 练习用的待办 API:登录拿令牌,增删改查待办。数据在内存里,重启清空。
// 运行:node server.js 监听 http://localhost:3000
import http from 'node:http';
import crypto from 'node:crypto';
const USERS = { alice: 'pass123', bob: 'hunter2' };
const tokens = new Map(); // token -> { user, exp }
let todos = []; let nextId = 1;
const TTL = Number(process.env.TOKEN_TTL_MS || 60 * 60 * 1000);
const send = (res, code, obj) => { res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(obj === undefined ? '' : JSON.stringify(obj)); };
const body = (req) => new Promise((ok) => { let d = ''; req.on('data', (c) => d += c); req.on('end', () => { try { ok(d ? JSON.parse(d) : {}); } catch { ok(null); } }); });
export const server = http.createServer(async (req, res) => {
const url = new URL(req.url, 'http://x');
if (req.method === 'POST' && url.pathname === '/login') {
const b = await body(req);
if (!b || USERS[b.username] !== b.password) return send(res, 401, { error: '用户名或密码错误' });
const token = crypto.randomBytes(16).toString('hex');
tokens.set(token, { user: b.username, exp: Date.now() + TTL });
return send(res, 200, { token, expires_in: TTL / 1000 });
}
const auth = (req.headers.authorization || '').replace(/^Bearer /, '');
const t = tokens.get(auth);
if (!t || t.exp < Date.now()) return send(res, 401, { error: '未登录或令牌过期' });
const m = url.pathname.match(/^\/todos(?:\/(\d+))?$/);
if (!m) return send(res, 404, { error: '没有这个接口' });
const id = m[1] && Number(m[1]);
const mine = todos.filter((x) => x.owner === t.user);
if (req.method === 'GET' && !id) return send(res, 200, mine);
if (req.method === 'POST' && !id) {
const b = await body(req);
if (!b || typeof b.title !== 'string' || !b.title.trim()) return send(res, 400, { error: 'title 必填' });
if (b.title.length > 100) return send(res, 400, { error: 'title 最多 100 字' });
const item = { id: nextId++, title: b.title.trim(), done: false, owner: t.user };
todos.push(item);
return send(res, 201, item);
}
const item = mine.find((x) => x.id === id);
if (!item) return send(res, 404, { error: '待办不存在' });
if (req.method === 'GET') return send(res, 200, item);
if (req.method === 'PATCH') { const b = await body(req); if (typeof b?.done === 'boolean') item.done = b.done; return send(res, 200, item); }
if (req.method === 'DELETE') { todos = todos.filter((x) => x !== item); return send(res, 204); }
send(res, 405, { error: '方法不支持' });
});
if (import.meta.url === `file://${process.argv[1]}`) server.listen(process.env.PORT || 3000, () => console.log('API 已启动 http://localhost:' + (process.env.PORT || 3000)));
node server.js
看到 API 已启动 http://localhost:3000 即可。用 curl 试一下:
curl -X POST localhost:3000/login -d '{"username":"alice","password":"pass123"}'
期望返回 {"token":"…32 位…","expires_in":3600}。
第二步:怎样在 Postman 里建集合?
1. 建环境
左侧 Environments → +,名称 local,添加三个变量:
| 变量 | 值 | 类型 |
|---|---|---|
baseUrl |
http://localhost:3000 |
default |
username |
alice |
default |
password |
pass123 |
secret |
右上角环境下拉框选中 local。
2. 建集合并设置统一鉴权
新建集合“待办 API 测试”,在集合的 Authorization 标签选 Bearer Token,Token 填 {{token}}。之后集合里的请求默认继承这个设置。
在集合的 Variables 标签添加 token、todoId 两个空变量。
3. 逐个添加请求和测试脚本
每个请求的脚本写在 Scripts → Post-response。带请求体的选 Body → raw → JSON。
1 登录
POST {{baseUrl}}/login,请求体 {"username": "{{username}}", "password": "{{password}}"} ,Authorization 选 No Auth
pm.test('状态码 200', () => pm.response.to.have.status(200));
const j = pm.response.json();
pm.test('返回 token', () => pm.expect(j.token).to.be.a('string').and.have.lengthOf(32));
pm.collectionVariables.set('token', j.token);
2 新建待办
POST {{baseUrl}}/todos,请求体 {"title": "买牛奶 {{$timestamp}}"}
pm.test('状态码 201', () => pm.response.to.have.status(201));
const j = pm.response.json();
pm.test('done 默认为 false', () => pm.expect(j.done).to.eql(false));
pm.test('响应时间小于 500ms', () => pm.expect(pm.response.responseTime).to.be.below(500));
pm.collectionVariables.set('todoId', j.id);
3 标记完成
PATCH {{baseUrl}}/todos/{{todoId}},请求体 {"done": true}
pm.test('done 变为 true', () => pm.expect(pm.response.json().done).to.eql(true));
4 列表包含新待办
GET {{baseUrl}}/todos
const id = Number(pm.collectionVariables.get('todoId'));
pm.test('列表里能找到', () => pm.expect(pm.response.json().map(t => t.id)).to.include(id));
pm.test('符合结构', () => pm.response.to.have.jsonSchema({
type: 'array',
items: { type: 'object', required: ['id', 'title', 'done'], properties: { id: { type: 'integer' }, title: { type: 'string' }, done: { type: 'boolean' } } }
}));
5 空标题应被拒绝
POST {{baseUrl}}/todos,请求体 {"title": ""}
pm.test('状态码 400', () => pm.response.to.have.status(400));
pm.test('有错误信息', () => pm.expect(pm.response.json().error).to.include('title'));
6 删除
DELETE {{baseUrl}}/todos/{{todoId}}
pm.test('状态码 204', () => pm.response.to.have.status(204));
7 删除后查询应 404
GET {{baseUrl}}/todos/{{todoId}}
pm.test('状态码 404', () => pm.response.to.have.status(404));
8 不带令牌应 401
GET {{baseUrl}}/todos ,Authorization 选 No Auth
pm.test('状态码 401', () => pm.response.to.have.status(401));
| 用法 | 作用 |
|---|---|
pm.test(名称, 函数) |
定义一条断言 |
pm.response.to.have.status() |
检查状态码 |
pm.collectionVariables.set() |
把值存到集合变量,后面的请求用 {{变量}} 引用 |
{{$timestamp}} |
内置动态变量,让每次标题不同 |
jsonSchema |
检查返回结构 |
懒得手动建的话,本文的
-src文件夹里有todo-api.postman_collection.json和local.postman_environment.json,在 Postman 里点 Import 导入即可。
第三步:怎样运行?
1. 在应用里运行
集合右侧 ⋯ → Run collection,保持顺序,点 Run。期望 8 个请求、13 条断言全部绿色。
2. 用命令行运行
npm i -g newman
newman run todo-api.postman_collection.json -e local.postman_environment.json
本文实际输出末尾:
│ requests │ 8 │ 0 │
│ test-scripts │ 8 │ 0 │
│ assertions │ 13 │ 0 │
也可以用 Postman 官方的 Postman CLI:
postman collection run todo-api.postman_collection.json -e local.postman_environment.json
本文实测未登录时也能运行本地集合文件,结束时会提示需要 postman login 才能把结果上传到 Postman 云端。
出问题时该怎么调?
1. 打开 Postman Console
底部 Console 能看到每个请求的完整请求头和响应,变量是否替换成功一看便知。脚本里也可以用 console.log()。
2. 第 2 个请求开始全部 401
| 原因 | 解决方法 |
|---|---|
| 登录请求没把令牌存进去 | 检查第 1 个请求的脚本 |
| 集合鉴权没设置 | 集合 Authorization 选 Bearer Token |
| 请求自己设置了 No Auth | 改回 Inherit auth from parent |
3. ECONNREFUSED
练习 API 没启动,或端口不是 3000。
常见问题怎么处理?
| 现象 | 原因 | 解决方法 |
|---|---|---|
{{todoId}} 原样发出去 |
变量没设置 | 必须按顺序从第 1 个跑 |
| 单独运行第 7 个失败 | 依赖前面的请求 | 整个集合一起跑 |
| 中文测试名乱码 | 终端编码 | 用 UTF-8 终端 |
| 响应时间断言偶尔失败 | 机器繁忙 | 适当放宽阈值 |
| 重启 API 后数据没了 | 练习 API 存在内存里 | 正常现象 |
项目资源
建议先导入现成的集合跑通一次,再自己从头建一遍,熟悉变量传递和断言写法。
然后把同样的思路用到你自己项目的接口上,先覆盖登录和最核心的两三个接口。