第七章:认证与安全机制
7.1 认证类型概览
7.1.1 Postman 支持的认证类型
Postman 认证类型
API Key
Bearer Token
Basic Auth
Digest Auth
OAuth 1.0
OAuth 2.0
JWT
Hawk
AWSSignature
NTLM
7.1.2 认证配置位置
认证配置层级:
1. Collection 级别:应用于集合内所有请求
2. Folder 级别:应用于文件夹内所有请求
3. Request 级别:仅应用于单个请求
优先级:Request > Folder > Collection
7.2 API Key 认证
7.2.1 API Key 认证原理
API 服务 客户端 API 服务 客户端 Key 可在 Header/Query/Body 中 请求携带 API Key 验证 Key 有效性 返回响应
7.2.2 API Key 配置方式
方式一:Header 传递
http
GET /api/v1/users HTTP/1.1
Host: api.example.com
X-API-Key: your-api-key-here
Postman 配置:
Authorization Tab:
Type: API Key
Key: X-API-Key
Value: {{apiKey}}
Add to: Header
方式二:Query 参数传递
http
GET /api/v1/users?api_key=your-api-key-here HTTP/1.1
Host: api.example.com
Postman 配置:
Authorization Tab:
Type: API Key
Key: api_key
Value: {{apiKey}}
Add to: Query Parameters
脚本动态设置:
javascript
// Pre-request Script 动态设置 API Key
const apiKey = pm.environment.get('apiKey');
const timestamp = Date.now();
// 生成带时间戳的签名 Key
const signedKey = CryptoJS.MD5(apiKey + timestamp).toString();
pm.request.headers.add({
key: 'X-API-Key',
value: signedKey
});
pm.request.headers.add({
key: 'X-Timestamp',
value: timestamp.toString()
});
7.3 Basic Auth 认证
7.3.1 Basic Auth 原理
Basic Auth 使用 Base64 编码的用户名和密码进行认证。
用户名:密码
Base64 编码
Authorization Header
Basic base64string
7.3.2 Basic Auth 配置
Postman 配置:
Authorization Tab:
Type: Basic Auth
Username: {{username}}
Password: {{password}}
手动设置请求头:
javascript
// Pre-request Script 设置 Basic Auth
const username = pm.environment.get('username');
const password = pm.environment.get('password');
// 生成 Basic Auth 头
const credentials = btoa(`${username}:${password}`);
pm.request.headers.upsert({
key: 'Authorization',
value: `Basic ${credentials}`
});
验证 Basic Auth 响应:
javascript
// Tests Script 验证 Basic Auth
pm.test('Basic Auth 认证应成功', function() {
pm.response.to.have.status(200);
});
pm.test('应返回用户信息', function() {
const response = pm.response.json();
pm.expect(response.data).to.have.property('username');
});
// 处理认证失败
if (pm.response.code === 401) {
const authHeader = pm.response.headers.get('WWW-Authenticate');
console.log('认证失败,服务器要求:', authHeader);
}
7.4 Bearer Token 认证
7.4.1 Bearer Token 原理
API 服务 认证服务 客户端 API 服务 认证服务 客户端 登录请求 返回 Access Token 请求携带 Bearer Token 验证 Token 返回响应
7.4.2 Bearer Token 配置
Postman 配置:
Authorization Tab:
Type: Bearer Token
Token: {{token}}
自动获取并保存 Token:
javascript
// 登录请求 Tests Script
const response = pm.response.json();
if (response.code === 200 && response.data.token) {
// 保存 Token
pm.environment.set('token', response.data.token);
// 保存过期时间
if (response.data.expiresIn) {
const expireTime = Date.now() + response.data.expiresIn * 1000;
pm.environment.set('tokenExpireTime', expireTime);
}
// 保存刷新 Token
if (response.data.refreshToken) {
pm.environment.set('refreshToken', response.data.refreshToken);
}
console.log('Token 已保存');
}
// 验证 Token 格式
pm.test('Token 格式应正确', function() {
pm.expect(response.data.token).to.be.a('string');
pm.expect(response.data.token.length).to.be.above(0);
});
Token 自动刷新机制:
javascript
// Pre-request Script - 检查并刷新 Token
const tokenExpireTime = parseInt(pm.environment.get('tokenExpireTime') || '0');
const refreshThreshold = 5 * 60 * 1000; // 5分钟前刷新
if (Date.now() > tokenExpireTime - refreshThreshold) {
console.log('Token 即将过期,尝试刷新...');
// 发送刷新请求
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/api/v1/auth/refresh',
method: 'POST',
header: {
'Content-Type': 'application/json'
},
body: {
mode: 'raw',
raw: JSON.stringify({
refreshToken: pm.environment.get('refreshToken')
})
}
}, (error, response) => {
if (!error && response.code === 200) {
const data = response.json();
pm.environment.set('token', data.data.token);
pm.environment.set('tokenExpireTime', Date.now() + data.data.expiresIn * 1000);
console.log('Token 刷新成功');
} else {
console.error('Token 刷新失败,请重新登录');
}
});
}
// 设置当前请求的 Token
const token = pm.environment.get('token');
if (token) {
pm.request.headers.upsert({
key: 'Authorization',
value: `Bearer ${token}`
});
}
7.5 OAuth 2.0 认证
7.5.1 OAuth 2.0 授权流程
资源服务器 授权服务器 客户端 用户 资源服务器 授权服务器 客户端 用户 点击授权 重定向到授权页面 显示授权页面 同意授权 返回授权码 使用授权码换取 Token 返回 Access Token 使用 Token 访问资源 返回资源
7.5.2 OAuth 2.0 授权类型
| 授权类型 | 适用场景 | 特点 |
|---|---|---|
| Authorization Code | Web 应用 | 最安全,需要授权码 |
| Implicit | 单页应用 | 直接返回 Token |
| Resource Owner Password | 受信任应用 | 直接使用用户名密码 |
| Client Credentials | 服务间通信 | 无用户参与 |
7.5.3 Authorization Code 流程配置
Postman 配置:
Authorization Tab:
Type: OAuth 2.0
Grant Type: Authorization Code
Callback URL: https://oauth.pstmn.io/v1/callback
Auth URL: https://auth.example.com/oauth/authorize
Access Token URL: https://auth.example.com/oauth/token
Client ID: {{clientId}}
Client Secret: {{clientSecret}}
Scope: read write
State: random_state_string
获取授权码:
javascript
// Pre-request Script - 构建授权 URL
const clientId = pm.environment.get('clientId');
const redirectUri = 'https://oauth.pstmn.io/v1/callback';
const scope = 'read write';
const state = Math.random().toString(36).substring(7);
// 保存 state 用于验证
pm.environment.set('oauthState', state);
const authUrl = `https://auth.example.com/oauth/authorize?` +
`response_type=code&` +
`client_id=${clientId}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`scope=${encodeURIComponent(scope)}&` +
`state=${state}`;
console.log('授权 URL:', authUrl);
使用授权码获取 Token:
javascript
// Tests Script - 处理授权码回调
const queryParams = pm.request.url.query.toObject();
const code = queryParams.code;
const state = queryParams.state;
// 验证 state
if (state !== pm.environment.get('oauthState')) {
console.error('State 不匹配,可能存在 CSRF 攻击');
}
if (code) {
// 使用授权码获取 Token
pm.sendRequest({
url: 'https://auth.example.com/oauth/token',
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'grant_type', value: 'authorization_code' },
{ key: 'code', value: code },
{ key: 'redirect_uri', value: 'https://oauth.pstmn.io/v1/callback' },
{ key: 'client_id', value: pm.environment.get('clientId') },
{ key: 'client_secret', value: pm.environment.get('clientSecret') }
]
}
}, (error, response) => {
if (!error) {
const data = response.json();
pm.environment.set('accessToken', data.access_token);
pm.environment.set('refreshToken', data.refresh_token);
pm.environment.set('tokenExpireTime', Date.now() + data.expires_in * 1000);
console.log('OAuth Token 获取成功');
}
});
}
7.5.4 Client Credentials 流程
javascript
// Client Credentials 授权
// Pre-request Script
const clientId = pm.environment.get('clientId');
const clientSecret = pm.environment.get('clientSecret');
// 检查是否需要获取新 Token
const tokenExpireTime = parseInt(pm.environment.get('clientTokenExpireTime') || '0');
if (Date.now() > tokenExpireTime - 60000) { // 提前1分钟刷新
// 获取 Client Credentials Token
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/oauth/token',
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(`${clientId}:${clientSecret}`)
},
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'grant_type', value: 'client_credentials' },
{ key: 'scope', value: 'api:read api:write' }
]
}
}, (error, response) => {
if (!error && response.code === 200) {
const data = response.json();
pm.environment.set('clientToken', data.access_token);
pm.environment.set('clientTokenExpireTime', Date.now() + data.expires_in * 1000);
console.log('Client Token 获取成功');
}
});
}
// 设置请求 Token
const clientToken = pm.environment.get('clientToken');
if (clientToken) {
pm.request.headers.upsert({
key: 'Authorization',
value: `Bearer ${clientToken}`
});
}
7.6 JWT 认证
7.6.1 JWT 结构解析
JWT Token
Header
Payload
Signature
alg: HS256
typ: JWT
用户信息
过期时间
自定义声明
签名验证
7.6.2 JWT 解析与验证
javascript
// JWT 解析函数
function parseJWT(token) {
try {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('无效的 JWT 格式');
}
const header = JSON.parse(atob(parts[0]));
const payload = JSON.parse(atob(parts[1]));
const signature = parts[2];
return {
header: header,
payload: payload,
signature: signature
};
} catch (error) {
console.error('JWT 解析失败:', error.message);
return null;
}
}
// 使用示例
const token = pm.environment.get('token');
const jwt = parseJWT(token);
if (jwt) {
console.log('JWT Header:', jwt.header);
console.log('JWT Payload:', jwt.payload);
// 验证过期时间
if (jwt.payload.exp) {
const expireTime = jwt.payload.exp * 1000;
if (Date.now() > expireTime) {
console.warn('JWT 已过期');
} else {
const remaining = Math.floor((expireTime - Date.now()) / 1000 / 60);
console.log(`JWT 剩余有效时间: ${remaining} 分钟`);
}
}
// 提取用户信息
if (jwt.payload.sub) {
pm.environment.set('jwtUserId', jwt.payload.sub);
}
if (jwt.payload.username) {
pm.environment.set('jwtUsername', jwt.payload.username);
}
}
7.6.3 JWT 签名验证
javascript
// JWT 签名验证
function verifyJWTSignature(token, secret) {
const parts = token.split('.');
if (parts.length !== 3) {
return false;
}
const header = parts[0];
const payload = parts[1];
const signature = parts[2];
// 重新计算签名
const expectedSignature = CryptoJS.HmacSHA256(
`${header}.${payload}`,
secret
).toString(CryptoJS.enc.Base64Url);
return signature === expectedSignature;
}
// 使用示例
const token = pm.environment.get('token');
const secret = pm.environment.get('jwtSecret');
pm.test('JWT 签名应有效', function() {
// 注意:在生产环境中,签名验证应在服务器端进行
// 这里仅用于测试目的
const isValid = verifyJWTSignature(token, secret);
pm.expect(isValid).to.be.true;
});
7.7 自定义认证方案
7.7.1 自定义签名认证
javascript
// 自定义签名认证方案
function generateSignature(params, secret) {
// 1. 按键名排序
const sortedKeys = Object.keys(params).sort();
// 2. 拼接参数字符串
const paramString = sortedKeys
.filter(key => params[key] !== undefined && params[key] !== '')
.map(key => `${key}=${params[key]}`)
.join('&');
// 3. 添加密钥
const signString = paramString + secret;
// 4. 计算签名
return CryptoJS.MD5(signString).toString().toUpperCase();
}
// Pre-request Script
const appId = pm.environment.get('appId');
const appSecret = pm.environment.get('appSecret');
const timestamp = Date.now();
const nonce = Math.random().toString(36).substring(2, 15);
// 构建签名参数
const signParams = {
appId: appId,
timestamp: timestamp,
nonce: nonce,
userId: pm.environment.get('userId')
};
// 生成签名
const signature = generateSignature(signParams, appSecret);
// 添加认证头
pm.request.headers.add({ key: 'X-App-ID', value: appId });
pm.request.headers.add({ key: 'X-Timestamp', value: timestamp.toString() });
pm.request.headers.add({ key: 'X-Nonce', value: nonce });
pm.request.headers.add({ key: 'X-Signature', value: signature });
console.log('签名参数:', signParams);
console.log('生成的签名:', signature);
7.7.2 双向证书认证
javascript
// 双向证书认证配置
// 在 Pre-request Script 中配置证书信息
// 注意:实际证书配置需要在 Postman 设置中进行
const certConfig = {
certPath: '/path/to/client.crt',
keyPath: '/path/to/client.key',
passphrase: pm.environment.get('certPassphrase')
};
// 验证服务器证书
pm.test('服务器证书应有效', function() {
const response = pm.response;
// 检查是否使用 HTTPS
pm.expect(pm.request.url.protocol).to.equal('https');
});
7.8 安全最佳实践
7.8.1 敏感信息保护
javascript
// 敏感信息保护最佳实践
// 1. 使用 Secret 类型变量
// 在环境变量中将敏感信息设置为 Secret 类型
// 2. 不在日志中输出敏感信息
const token = pm.environment.get('token');
// 错误做法
// console.log('Token:', token);
// 正确做法
console.log('Token 已获取,长度:', token ? token.length : 0);
// 3. 请求完成后清除临时敏感数据
pm.environment.unset('tempPassword');
pm.variables.unset('tempKey');
// 4. 使用环境变量而非硬编码
// 错误做法
// const apiKey = 'sk-xxxxx';
// 正确做法
const apiKey = pm.environment.get('apiKey');
// 5. 定期轮换密钥
const keyLastRotated = parseInt(pm.environment.get('keyLastRotated') || '0');
const rotationPeriod = 90 * 24 * 60 * 60 * 1000; // 90天
if (Date.now() - keyLastRotated > rotationPeriod) {
console.warn('API Key 已超过 90 天未轮换,建议更新');
}
7.8.2 安全测试检查清单
javascript
// 安全测试检查清单
const response = pm.response.json();
// 1. 验证 HTTPS
pm.test('应使用 HTTPS 协议', function() {
pm.expect(pm.request.url.protocol).to.equal('https');
});
// 2. 验证安全响应头
pm.test('应包含安全响应头', function() {
const headers = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': /.*/
};
Object.entries(headers).forEach(([header, expected]) => {
const value = pm.response.headers.get(header);
if (expected instanceof RegExp) {
pm.expect(value).to.match(expected);
} else {
pm.expect(value).to.equal(expected);
}
});
});
// 3. 验证不暴露敏感信息
pm.test('响应不应包含敏感字段', function() {
const sensitiveFields = ['password', 'secret', 'apiKey', 'privateKey'];
const responseStr = JSON.stringify(response);
sensitiveFields.forEach(field => {
pm.expect(responseStr.toLowerCase()).to.not.include(field.toLowerCase());
});
});
// 4. 验证错误信息不泄露系统信息
pm.test('错误信息不应泄露系统信息', function() {
if (response.message) {
const systemPatterns = [
/stack trace/i,
/exception/i,
/sql/i,
/path/i,
/file not found/i
];
systemPatterns.forEach(pattern => {
pm.expect(response.message).to.not.match(pattern);
});
}
});
// 5. 验证认证失败不暴露详细信息
if (pm.response.code === 401) {
pm.test('认证失败应返回通用错误信息', function() {
pm.expect(response.message).to.not.include('password');
pm.expect(response.message).to.not.include('user not found');
});
}
7.8.3 防重放攻击
javascript
// 防重放攻击机制
// Pre-request Script
const timestamp = Date.now();
const nonce = 'nonce_' + Math.random().toString(36).substring(2, 15);
// 添加时间戳和随机数
pm.request.headers.add({ key: 'X-Timestamp', value: timestamp.toString() });
pm.request.headers.add({ key: 'X-Nonce', value: nonce });
// 记录已使用的 nonce(用于测试验证)
const usedNonces = JSON.parse(pm.environment.get('usedNonces') || '[]');
usedNonces.push({ nonce: nonce, timestamp: timestamp });
// 只保留最近 1000 个
if (usedNonces.length > 1000) {
usedNonces.shift();
}
pm.environment.set('usedNonces', JSON.stringify(usedNonces));
// Tests Script - 验证服务器拒绝重放
pm.test('服务器应拒绝过期的请求', function() {
// 如果请求时间戳过期,应返回 401 或 403
const requestTime = parseInt(pm.request.headers.get('X-Timestamp'));
const maxAge = 5 * 60 * 1000; // 5分钟
if (Date.now() - requestTime > maxAge) {
pm.expect([401, 403]).to.include(pm.response.code);
}
});
第八章:集合管理与数据驱动测试
8.1 Collection 结构设计
8.1.1 Collection 组织原则
Collection
Folder: 用户模块
Folder: 订单模块
Folder: 商品模块
登录
注册
获取用户信息
创建订单
查询订单
取消订单
商品列表
商品详情
8.1.2 Collection 变量与脚本
Collection 级别变量:
json
{
"variable": [
{ "key": "baseUrl", "value": "https://api.example.com" },
{ "key": "apiVersion", "value": "v1" },
{ "key": "timeout", "value": "30000" },
{ "key": "retryCount", "value": "3" }
]
}
Collection 级别 Pre-request Script:
javascript
// Collection Pre-request Script
// 应用于集合内所有请求
// 设置公共请求头
pm.request.headers.add({
key: 'X-API-Version',
value: pm.collectionVariables.get('apiVersion')
});
pm.request.headers.add({
key: 'X-Client-ID',
value: 'Postman-AutoTest-v1.0'
});
// 自动添加认证
const token = pm.environment.get('token');
if (token) {
pm.request.headers.upsert({
key: 'Authorization',
value: `Bearer ${token}`
});
}
// 请求追踪
const traceId = 'trace_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
pm.request.headers.add({ key: 'X-Trace-ID', value: traceId });
pm.variables.set('currentTraceId', traceId);
Collection 级别 Tests Script:
javascript
// Collection Tests Script
// 应用于集合内所有请求
// 记录请求信息
console.log('请求完成:', {
url: pm.request.url.toString(),
method: pm.request.method,
status: pm.response.code,
time: pm.response.responseTime + 'ms',
traceId: pm.variables.get('currentTraceId')
});
// 公共断言
pm.test('响应时间应小于 5 秒', function() {
pm.expect(pm.response.responseTime).to.be.below(5000);
});
// 错误处理
if (pm.response.code >= 400) {
console.error('请求失败:', {
status: pm.response.code,
body: pm.response.text()
});
}
8.2 Collection Runner 使用
8.2.1 Collection Runner 界面
选择 Collection
配置运行参数
选择环境
设置迭代次数
设置延迟
选择数据文件
运行
8.2.2 运行参数配置
| 参数 | 说明 | 推荐值 |
|---|---|---|
| Iterations | 迭代次数 | 根据数据文件行数 |
| Delay | 请求间隔 | 100-500ms |
| Data File | 数据文件 | CSV/JSON |
| Persist variables | 保存变量 | 按需选择 |
| Stop on error | 遇错停止 | 测试时开启 |
8.2.3 运行结果分析
javascript
// 运行结果统计脚本
// 在 Collection Tests 中添加
const results = {
passed: 0,
failed: 0,
skipped: 0,
total: pm.info.iterationCount
};
// 获取当前测试结果
const testResults = pm.test.results();
results.passed = testResults.passed;
results.failed = testResults.failed;
// 计算成功率
const successRate = (results.passed / (results.passed + results.failed) * 100).toFixed(2);
console.log(`成功率: ${successRate}%`);
// 保存结果
pm.environment.set('testResults', JSON.stringify(results));
8.3 数据驱动测试
8.3.1 数据驱动测试原理
数据文件
Collection Runner
迭代执行
第1行数据
第2行数据
第3行数据
...
执行请求
生成测试报告
8.3.2 CSV 数据文件
数据文件格式:
csv
username,password,email,expectedStatus
testuser1,Pass123!,test1@example.com,200
testuser2,Pass456!,test2@example.com,200
testuser3,short,test3@example.com,400
testuser4,,test4@example.com,400
使用 CSV 数据:
javascript
// Pre-request Script - 使用 CSV 数据
// 获取当前迭代的数据
const username = pm.iterationData.get('username');
const password = pm.iterationData.get('password');
const email = pm.iterationData.get('email');
const expectedStatus = pm.iterationData.get('expectedStatus');
// 设置请求体
const requestBody = {
username: username,
password: password,
email: email
};
pm.request.body.raw = JSON.stringify(requestBody);
// 保存期望状态码供测试使用
pm.variables.set('expectedStatus', parseInt(expectedStatus));
console.log(`测试数据: 用户名=${username}, 期望状态=${expectedStatus}`);
Tests Script - 验证结果:
javascript
// Tests Script - 数据驱动验证
const expectedStatus = pm.variables.get('expectedStatus');
const username = pm.iterationData.get('username');
pm.test(`用户 ${username} 注册应返回 ${expectedStatus}`, function() {
pm.response.to.have.status(expectedStatus);
});
// 根据期望状态进行不同验证
if (expectedStatus === 200) {
pm.test('成功注册应返回用户ID', function() {
const response = pm.response.json();
pm.expect(response.data).to.have.property('id');
pm.expect(response.data.username).to.equal(username);
});
} else {
pm.test('失败注册应返回错误信息', function() {
const response = pm.response.json();
pm.expect(response).to.have.property('message');
});
}
8.3.3 JSON 数据文件
JSON 数据文件格式:
json
[
{
"testCase": "正常登录",
"username": "admin",
"password": "Admin123!",
"expectedStatus": 200,
"expectedRole": "admin"
},
{
"testCase": "错误密码",
"username": "admin",
"password": "wrongpassword",
"expectedStatus": 401,
"expectedMessage": "密码错误"
},
{
"testCase": "用户不存在",
"username": "nonexistent",
"password": "anypassword",
"expectedStatus": 401,
"expectedMessage": "用户不存在"
},
{
"testCase": "空用户名",
"username": "",
"password": "anypassword",
"expectedStatus": 400,
"expectedMessage": "用户名不能为空"
}
]
使用 JSON 数据:
javascript
// Pre-request Script - 使用 JSON 数据
const testCase = pm.iterationData.get('testCase');
const username = pm.iterationData.get('username');
const password = pm.iterationData.get('password');
const expectedStatus = pm.iterationData.get('expectedStatus');
const expectedRole = pm.iterationData.get('expectedRole');
const expectedMessage = pm.iterationData.get('expectedMessage');
console.log(`执行测试用例: ${testCase}`);
// 设置请求体
pm.request.body.raw = JSON.stringify({
username: username,
password: password
});
// 保存期望值
pm.variables.set('expectedStatus', expectedStatus);
pm.variables.set('expectedRole', expectedRole);
pm.variables.set('expectedMessage', expectedMessage);
pm.variables.set('testCase', testCase);
Tests Script - JSON 数据验证:
javascript
// Tests Script - JSON 数据驱动验证
const testCase = pm.variables.get('testCase');
const expectedStatus = pm.variables.get('expectedStatus');
const expectedRole = pm.variables.get('expectedRole');
const expectedMessage = pm.variables.get('expectedMessage');
pm.test(`[${testCase}] 状态码应为 ${expectedStatus}`, function() {
pm.response.to.have.status(expectedStatus);
});
if (expectedStatus === 200) {
pm.test(`[${testCase}] 应返回正确的角色`, function() {
const response = pm.response.json();
pm.expect(response.data.role).to.equal(expectedRole);
});
} else {
pm.test(`[${testCase}] 应返回正确的错误信息`, function() {
const response = pm.response.json();
pm.expect(response.message).to.include(expectedMessage);
});
}
8.4 批量执行策略
8.4.1 顺序执行
登录
获取用户信息
创建订单
支付订单
查询订单状态
实现顺序执行:
javascript
// 使用 postman.setNextRequest 实现顺序执行
// 登录请求 Tests Script
const response = pm.response.json();
if (response.code === 200) {
pm.environment.set('token', response.data.token);
pm.environment.set('userId', response.data.user.id);
postman.setNextRequest('获取用户信息');
} else {
console.error('登录失败,停止执行');
postman.setNextRequest(null);
}
// 获取用户信息 Tests Script
if (pm.response.code === 200) {
postman.setNextRequest('创建订单');
} else {
postman.setNextRequest(null);
}
// 创建订单 Tests Script
if (pm.response.code === 200) {
const response = pm.response.json();
pm.environment.set('orderId', response.data.id);
postman.setNextRequest('支付订单');
} else {
postman.setNextRequest(null);
}
8.4.2 并行执行策略
javascript
// 并行执行多个独立请求
// Pre-request Script
const requests = [
{ name: '获取用户列表', url: '/api/v1/users' },
{ name: '获取商品列表', url: '/api/v1/products' },
{ name: '获取订单列表', url: '/api/v1/orders' }
];
const results = {};
let completed = 0;
requests.forEach(req => {
pm.sendRequest({
url: pm.environment.get('baseUrl') + req.url,
method: 'GET',
header: {
'Authorization': 'Bearer ' + pm.environment.get('token')
}
}, (error, response) => {
completed++;
if (!error) {
results[req.name] = {
status: response.code,
data: response.json().data,
time: response.responseTime
};
} else {
results[req.name] = { error: error.message };
}
if (completed === requests.length) {
pm.variables.set('parallelResults', JSON.stringify(results));
console.log('所有并行请求完成:', results);
}
});
});
8.4.3 条件执行
javascript
// 根据条件决定执行路径
const response = pm.response.json();
const userType = response.data.type;
switch (userType) {
case 'admin':
postman.setNextRequest('管理员操作');
break;
case 'user':
postman.setNextRequest('普通用户操作');
break;
case 'guest':
postman.setNextRequest('游客操作');
break;
default:
console.warn('未知用户类型:', userType);
postman.setNextRequest(null);
}
8.5 测试报告生成
8.5.1 Newman 命令行运行
bash
# 基本运行
newman run collection.json -e environment.json
# 指定迭代次数和数据文件
newman run collection.json -e environment.json -n 10 -d data.csv
# 生成 HTML 报告
newman run collection.json -e environment.json -r html --reporter-html-export report.html
# 生成多种格式报告
newman run collection.json -e environment.json -r cli,html,json --reporter-html-export report.html --reporter-json-export report.json
# 设置超时和重试
newman run collection.json -e environment.json --timeout-request 30000 --delay-request 500
# 忽略错误继续执行
newman run collection.json -e environment.json --bail
# 详细输出
newman run collection.json -e environment.json --verbose
8.5.2 自定义报告模板
javascript
// 在 Collection Tests 中收集报告数据
const reportData = {
timestamp: new Date().toISOString(),
request: {
method: pm.request.method,
url: pm.request.url.toString(),
headers: pm.request.headers.toObject()
},
response: {
status: pm.response.code,
time: pm.response.responseTime,
size: pm.response.size()
},
tests: pm.test.results(),
iteration: pm.info.iteration + 1
};
// 保存到环境变量
const allReports = JSON.parse(pm.environment.get('testReports') || '[]');
allReports.push(reportData);
pm.environment.set('testReports', JSON.stringify(allReports));
8.5.3 测试结果导出
javascript
// Tests Script - 导出测试结果
// 在最后一个请求的 Tests Script 中执行
if (pm.info.iteration === pm.info.iterationCount - 1) {
const reports = JSON.parse(pm.environment.get('testReports') || '[]');
// 计算统计信息
const summary = {
total: reports.length,
passed: reports.filter(r => r.tests.passed > 0 && r.tests.failed === 0).length,
failed: reports.filter(r => r.tests.failed > 0).length,
avgResponseTime: reports.reduce((sum, r) => sum + r.response.time, 0) / reports.length
};
console.log('=== 测试报告摘要 ===');
console.log(`总请求数: ${summary.total}`);
console.log(`通过: ${summary.passed}`);
console.log(`失败: ${summary.failed}`);
console.log(`平均响应时间: ${summary.avgResponseTime.toFixed(2)}ms`);
// 清理环境变量
pm.environment.unset('testReports');
}
第九章:高级自动化场景实战
9.1 接口依赖处理
9.1.1 依赖链管理
登录获取Token
获取用户ID
创建订单
获取订单ID
支付订单
验证支付状态
实现依赖链:
javascript
// 请求1: 登录
// Tests Script
const loginResponse = pm.response.json();
if (loginResponse.code === 200) {
pm.environment.set('token', loginResponse.data.token);
pm.environment.set('userId', loginResponse.data.user.id);
console.log('登录成功,Token 已保存');
}
// 请求2: 获取用户详情
// Pre-request Script
pm.request.headers.upsert({
key: 'Authorization',
value: 'Bearer ' + pm.environment.get('token')
});
// Tests Script
const userResponse = pm.response.json();
if (userResponse.code === 200) {
pm.environment.set('userBalance', userResponse.data.balance);
}
// 请求3: 创建订单
// Pre-request Script
pm.request.headers.upsert({
key: 'Authorization',
value: 'Bearer ' + pm.environment.get('token')
});
const orderBody = {
userId: pm.environment.get('userId'),
items: [
{ productId: 'P001', quantity: 2 }
]
};
pm.request.body.raw = JSON.stringify(orderBody);
// Tests Script
const orderResponse = pm.response.json();
if (orderResponse.code === 200) {
pm.environment.set('orderId', orderResponse.data.id);
pm.environment.set('orderAmount', orderResponse.data.totalAmount);
}
9.1.2 动态依赖处理
javascript
// 动态处理接口依赖
// Pre-request Script - 检查依赖
function checkDependencies() {
const dependencies = {
token: pm.environment.get('token'),
userId: pm.environment.get('userId')
};
const missing = Object.entries(dependencies)
.filter(([key, value]) => !value)
.map(([key]) => key);
if (missing.length > 0) {
console.error('缺少依赖:', missing.join(', '));
return false;
}
return true;
}
if (!checkDependencies()) {
// 跳过当前请求,执行前置请求
postman.setNextRequest('登录');
}
9.2 Mock Server 使用
9.2.1 Mock Server 创建
创建 Mock Server
定义 Mock 响应
配置请求匹配
生成 Mock URL
使用 Mock URL 测试
Mock 响应示例:
json
{
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"code": 200,
"message": "成功",
"data": {
"id": 123,
"name": "测试用户",
"email": "test@example.com"
}
}
}
9.2.2 动态 Mock 响应
javascript
// Mock Server 动态响应脚本
// 在 Mock Server 的响应中使用模板
{
"code": 200,
"message": "成功",
"data": {
"id": {{randomInt}},
"name": "用户_{{randomName}}",
"email": "user_{{randomInt}}@example.com",
"createdAt": "{{now}}"
}
}
9.3 WebSocket 测试
9.3.1 WebSocket 连接
javascript
// WebSocket 连接示例
// 注意:Postman 支持 WebSocket 测试
// 在 WebSocket 请求中:
// 连接 URL
// ws://echo.websocket.org
// 发送消息
{
"type": "ping",
"timestamp": Date.now()
}
// 接收消息验证
pm.test('WebSocket 应返回相同消息', function() {
const received = pm.response.json();
pm.expect(received.type).to.equal('ping');
});
9.4 GraphQL 测试
9.4.1 GraphQL 请求配置
javascript
// GraphQL 请求示例
// 请求方法: POST
// URL: {{baseUrl}}/graphql
// Content-Type: application/json
// 请求体
const query = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
orders {
id
amount
status
}
}
}
`;
const variables = {
id: pm.environment.get('userId')
};
pm.request.body.raw = JSON.stringify({
query: query,
variables: variables
});
9.4.2 GraphQL 响应验证
javascript
// Tests Script - GraphQL 响应验证
const response = pm.response.json();
// 验证无错误
pm.test('GraphQL 响应不应包含错误', function() {
pm.expect(response.errors).to.be.undefined;
});
// 验证数据结构
pm.test('GraphQL 应返回用户数据', function() {
pm.expect(response.data).to.have.property('user');
pm.expect(response.data.user).to.have.property('id');
pm.expect(response.data.user).to.have.property('name');
});
// 验证嵌套数据
pm.test('用户订单应为数组', function() {
pm.expect(response.data.user.orders).to.be.an('array');
});
9.5 文件上传下载测试
9.5.1 文件上传
javascript
// 文件上传配置
// 请求方法: POST
// Body 类型: form-data
// 添加文件字段
// Key: file (type: File)
// Value: 选择要上传的文件
// 额外参数
pm.request.body.formdata.add({
key: 'description',
value: '文件描述'
});
// Tests Script
pm.test('文件上传应成功', function() {
pm.response.to.have.status(200);
const response = pm.response.json();
pm.expect(response.data).to.have.property('fileId');
pm.expect(response.data).to.have.property('url');
});
9.5.2 文件下载
javascript
// 文件下载测试
// 请求方法: GET
// URL: {{baseUrl}}/api/v1/files/{{fileId}}
// Tests Script
pm.test('文件下载应成功', function() {
pm.response.to.have.status(200);
// 验证 Content-Type
const contentType = pm.response.headers.get('Content-Type');
pm.expect(contentType).to.exist;
// 验证 Content-Disposition
const disposition = pm.response.headers.get('Content-Disposition');
pm.expect(disposition).to.include('attachment');
// 验证文件大小
const size = pm.response.size();
pm.expect(size.body).to.be.above(0);
console.log('文件大小:', size.body, 'bytes');
});
9.6 性能测试基础
9.6.1 响应时间监控
javascript
// 响应时间监控脚本
const responseTime = pm.response.responseTime;
const endpoint = pm.request.url.path.join('/');
// 定义阈值
const thresholds = {
'/api/v1/users': { good: 200, acceptable: 500 },
'/api/v1/orders': { good: 300, acceptable: 800 },
'/api/v1/reports': { good: 1000, acceptable: 3000 }
};
const threshold = thresholds[endpoint] || { good: 500, acceptable: 1000 };
// 评估响应时间
if (responseTime < threshold.good) {
console.log(`✓ 响应时间优秀: ${responseTime}ms`);
} else if (responseTime < threshold.acceptable) {
console.log(`⚠ 响应时间可接受: ${responseTime}ms`);
} else {
console.log(`✗ 响应时间过长: ${responseTime}ms`);
}
// 记录历史数据
const history = JSON.parse(pm.environment.get('performanceHistory') || '{}');
if (!history[endpoint]) {
history[endpoint] = [];
}
history[endpoint].push({
time: responseTime,
timestamp: Date.now()
});
// 只保留最近 100 条
if (history[endpoint].length > 100) {
history[endpoint].shift();
}
pm.environment.set('performanceHistory', JSON.stringify(history));
// 计算平均值
const avgTime = history[endpoint].reduce((sum, r) => sum + r.time, 0) / history[endpoint].length;
console.log(`平均响应时间: ${avgTime.toFixed(2)}ms`);
9.6.2 并发测试模拟
javascript
// 并发测试模拟
const concurrentRequests = 10;
const completed = [];
let finished = 0;
for (let i = 0; i < concurrentRequests; i++) {
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/api/v1/test',
method: 'GET',
header: {
'Authorization': 'Bearer ' + pm.environment.get('token')
}
}, (error, response) => {
finished++;
completed.push({
index: i,
status: response.code,
time: response.responseTime,
error: error ? error.message : null
});
if (finished === concurrentRequests) {
// 所有请求完成,分析结果
const successCount = completed.filter(r => r.status === 200).length;
const avgTime = completed.reduce((sum, r) => sum + r.time, 0) / completed.length;
const maxTime = Math.max(...completed.map(r => r.time));
const minTime = Math.min(...completed.map(r => r.time));
console.log('=== 并发测试结果 ===');
console.log(`并发数: ${concurrentRequests}`);
console.log(`成功数: ${successCount}`);
console.log(`成功率: ${(successCount / concurrentRequests * 100).toFixed(2)}%`);
console.log(`平均响应时间: ${avgTime.toFixed(2)}ms`);
console.log(`最大响应时间: ${maxTime}ms`);
console.log(`最小响应时间: ${minTime}ms`);
}
});
}
第十章:生产环境最佳实践
10.1 Newman CLI 深度应用
10.1.1 Newman 完整配置
javascript
// newman.config.js
module.exports = {
collection: './collections/api-tests.postman_collection.json',
environment: './environments/production.postman_environment.json',
globals: './globals/global-variables.postman_globals.json',
iterationData: './data/test-data.csv',
iterationCount: 10,
delayRequest: 500,
timeoutRequest: 30000,
timeoutScript: 60000,
reporters: ['cli', 'html', 'json', 'junit'],
reporter: {
html: {
export: './reports/html/report.html',
template: './templates/custom-template.hbs',
title: 'API 测试报告',
showEnvironmentData: true,
showGlobalData: false
},
json: {
export: './reports/json/report.json'
},
junit: {
export: './reports/junit/report.xml'
}
},
bail: true,
suppressExitCode: false,
ignoreRedirects: false,
insecureFileRead: true
};
10.1.2 Newman 运行脚本
javascript
// run-tests.js
const newman = require('newman');
const fs = require('fs');
const path = require('path');
// 获取命令行参数
const env = process.argv[2] || 'dev';
const tags = process.argv[3] ? process.argv[3].split(',') : [];
// 加载配置
const config = require('./newman.config.js');
config.environment = `./environments/${env}.postman_environment.json`;
// 添加时间戳到报告文件名
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
config.reporter.html.export = `./reports/html/report-${timestamp}.html`;
config.reporter.json.export = `./reports/json/report-${timestamp}.json`;
// 运行测试
newman.run(config, function(err, summary) {
if (err) {
console.error('测试运行错误:', err);
process.exit(1);
}
// 输出摘要
console.log('\n=== 测试摘要 ===');
console.log(`总请求数: ${summary.stats.requests.total}`);
console.log(`成功: ${summary.stats.requests.total - summary.stats.requests.failed}`);
console.log(`失败: ${summary.stats.requests.failed}`);
console.log(`断言总数: ${summary.stats.assertions.total}`);
console.log(`断言失败: ${summary.stats.assertions.failed}`);
// 根据结果设置退出码
if (summary.run.failures.length > 0) {
process.exit(1);
}
});
10.2 CI/CD 集成
10.2.1 Jenkins 集成
groovy
// Jenkinsfile
pipeline {
agent any
environment {
POSTMAN_COLLECTION = 'collections/api-tests.postman_collection.json'
REPORT_PATH = 'reports/html'
}
stages {
stage('准备环境') {
steps {
sh 'npm install -g newman newman-reporter-html'
sh 'mkdir -p reports/html reports/json reports/junit'
}
}
stage('运行开发环境测试') {
when {
branch 'develop'
}
steps {
sh """
newman run ${POSTMAN_COLLECTION} \
-e environments/dev.postman_environment.json \
-r html,json,junit \
--reporter-html-export reports/html/dev-report.html \
--reporter-json-export reports/json/dev-report.json \
--reporter-junit-export reports/junit/dev-report.xml
"""
}
}
stage('运行测试环境测试') {
when {
branch 'release/*'
}
steps {
sh """
newman run ${POSTMAN_COLLECTION} \
-e environments/test.postman_environment.json \
-r html,json,junit \
--reporter-html-export reports/html/test-report.html \
--reporter-json-export reports/json/test-report.json \
--reporter-junit-export reports/junit/test-report.xml
"""
}
}
stage('运行生产环境冒烟测试') {
when {
branch 'main'
}
steps {
sh """
newman run ${POSTMAN_COLLECTION} \
-e environments/prod.postman_environment.json \
--folder '冒烟测试' \
-r html,json,junit \
--reporter-html-export reports/html/prod-report.html \
--reporter-json-export reports/json/prod-report.json \
--reporter-junit-export reports/junit/prod-report.xml
"""
}
}
}
post {
always {
// 发布测试报告
publishHTML(target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports/html',
reportFiles: '*.html',
reportName: 'Postman Test Report'
])
// 发布 JUnit 结果
junit 'reports/junit/*.xml'
}
failure {
mail to: 'team@example.com',
subject: "API 测试失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: "请查看测试报告: ${env.BUILD_URL}"
}
}
}
10.2.2 GitHub Actions 集成
yaml
# .github/workflows/api-tests.yml
name: API Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 6 * * *' # 每天 6:00 UTC 执行
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, test]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Newman
run: |
npm install -g newman
npm install -g newman-reporter-html
- name: Run API Tests
run: |
newman run collections/api-tests.postman_collection.json \
-e environments/${{ matrix.environment }}.postman_environment.json \
-r html,json \
--reporter-html-export reports/report-${{ matrix.environment }}.html \
--reporter-json-export reports/report-${{ matrix.environment }}.json
- name: Upload Test Reports
uses: actions/upload-artifact@v3
if: always()
with:
name: test-reports-${{ matrix.environment }}
path: reports/
- name: Comment PR with Results
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('reports/report-${{ matrix.environment }}.json'));
const stats = report.run.stats;
const body = `## API 测试结果 (${{ matrix.environment }})
| 指标 | 数值 |
|------|------|
| 总请求数 | ${stats.requests.total} |
| 失败请求数 | ${stats.requests.failed} |
| 断言总数 | ${stats.assertions.total} |
| 断言失败 | ${stats.assertions.failed} |
| 平均响应时间 | ${stats.averages.responseTime.toFixed(2)}ms |
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
10.3 测试数据管理
10.3.1 测试数据准备
javascript
// 测试数据准备脚本
// Pre-request Script - 数据准备
async function prepareTestData() {
// 检查是否已有测试数据
if (pm.environment.has('testDataPrepared')) {
return;
}
// 创建测试用户
const userResponse = await new Promise((resolve, reject) => {
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/api/v1/test/users',
method: 'POST',
header: { 'Content-Type': 'application/json' },
body: {
mode: 'raw',
raw: JSON.stringify({
username: 'test_user_' + Date.now(),
password: 'Test123!',
email: 'test@example.com'
})
}
}, (error, response) => {
if (error) reject(error);
else resolve(response);
});
});
const userData = userResponse.json();
pm.environment.set('testUserId', userData.data.id);
pm.environment.set('testUserToken', userData.data.token);
// 创建测试订单
const orderResponse = await new Promise((resolve, reject) => {
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/api/v1/test/orders',
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + userData.data.token
},
body: {
mode: 'raw',
raw: JSON.stringify({
items: [{ productId: 'P001', quantity: 1 }]
})
}
}, (error, response) => {
if (error) reject(error);
else resolve(response);
});
});
const orderData = orderResponse.json();
pm.environment.set('testOrderId', orderData.data.id);
pm.environment.set('testDataPrepared', 'true');
console.log('测试数据准备完成');
}
prepareTestData();
10.3.2 测试数据清理
javascript
// Tests Script - 数据清理
// 在最后一个测试请求后执行清理
if (pm.info.iteration === pm.info.iterationCount - 1) {
// 清理测试数据
pm.sendRequest({
url: pm.environment.get('baseUrl') + '/api/v1/test/cleanup',
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + pm.environment.get('testUserToken')
},
body: {
mode: 'raw',
raw: JSON.stringify({
userId: pm.environment.get('testUserId'),
orderId: pm.environment.get('testOrderId')
})
}
}, (error, response) => {
if (!error && response.code === 200) {
console.log('测试数据清理完成');
// 清除环境变量
pm.environment.unset('testUserId');
pm.environment.unset('testUserToken');
pm.environment.unset('testOrderId');
pm.environment.unset('testDataPrepared');
}
});
}
10.4 测试报告与监控
10.4.1 自定义报告生成
javascript
// 自定义报告生成脚本
function generateCustomReport() {
const reports = JSON.parse(pm.environment.get('testReports') || '[]');
const summary = {
title: 'API 自动化测试报告',
generatedAt: new Date().toISOString(),
environment: pm.environment.name,
totalRequests: reports.length,
passedRequests: reports.filter(r => r.response.status >= 200 && r.response.status < 300).length,
failedRequests: reports.filter(r => r.response.status >= 400).length,
totalTests: reports.reduce((sum, r) => sum + r.tests.passed + r.tests.failed, 0),
passedTests: reports.reduce((sum, r) => sum + r.tests.passed, 0),
failedTests: reports.reduce((sum, r) => sum + r.tests.failed, 0),
avgResponseTime: reports.reduce((sum, r) => sum + r.response.time, 0) / reports.length,
maxResponseTime: Math.max(...reports.map(r => r.response.time)),
minResponseTime: Math.min(...reports.map(r => r.response.time))
};
summary.successRate = (summary.passedTests / summary.totalTests * 100).toFixed(2) + '%';
// 生成 HTML 报告
const html = `
<!DOCTYPE html>
<html>
<head>
<title>${summary.title}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.summary { background: #f5f5f5; padding: 20px; border-radius: 5px; }
.metric { display: inline-block; margin: 10px 20px; }
.metric-value { font-size: 24px; font-weight: bold; }
.metric-label { color: #666; }
.pass { color: #4caf50; }
.fail { color: #f44336; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #f5f5f5; }
</style>
</head>
<body>
<h1>${summary.title}</h1>
<p>生成时间: ${summary.generatedAt}</p>
<p>测试环境: ${summary.environment}</p>
<div class="summary">
<div class="metric">
<div class="metric-value">${summary.totalRequests}</div>
<div class="metric-label">总请求数</div>
</div>
<div class="metric">
<div class="metric-value pass">${summary.passedRequests}</div>
<div class="metric-label">通过请求</div>
</div>
<div class="metric">
<div class="metric-value fail">${summary.failedRequests}</div>
<div class="metric-label">失败请求</div>
</div>
<div class="metric">
<div class="metric-value">${summary.successRate}</div>
<div class="metric-label">成功率</div>
</div>
<div class="metric">
<div class="metric-value">${summary.avgResponseTime.toFixed(2)}ms</div>
<div class="metric-label">平均响应时间</div>
</div>
</div>
<h2>详细结果</h2>
<table>
<tr>
<th>请求</th>
<th>方法</th>
<th>状态码</th>
<th>响应时间</th>
<th>断言结果</th>
</tr>
${reports.map(r => `
<tr>
<td>${r.request.url}</td>
<td>${r.request.method}</td>
<td class="${r.response.status < 300 ? 'pass' : 'fail'}">${r.response.status}</td>
<td>${r.response.time}ms</td>
<td>${r.tests.passed}/${r.tests.passed + r.tests.failed}</td>
</tr>
`).join('')}
</table>
</body>
</html>
`;
// 保存报告(在实际使用中需要写入文件)
pm.environment.set('customReport', html);
console.log('自定义报告已生成');
}
// 在测试结束时调用
if (pm.info.iteration === pm.info.iterationCount - 1) {
generateCustomReport();
}
10.5 团队协作最佳实践
10.5.1 Collection 版本管理
项目结构:
├── collections/
│ ├── v1.0/
│ │ └── api-tests.postman_collection.json
│ ├── v1.1/
│ │ └── api-tests.postman_collection.json
│ └── latest/
│ └── api-tests.postman_collection.json
├── environments/
│ ├── dev.postman_environment.json
│ ├── test.postman_environment.json
│ └── prod.postman_environment.json
├── data/
│ ├── test-data.csv
│ └── test-data.json
├── reports/
│ ├── html/
│ ├── json/
│ └── junit/
├── scripts/
│ ├── run-tests.js
│ └── newman.config.js
└── README.md
10.5.2 命名规范
Collection 命名规范:
- 项目名称-API名称-版本
- 示例:UserAPI-用户管理-v1.0
Request 命名规范:
- 模块-操作-描述
- 示例:用户-登录-正常登录
Folder 命名规范:
- 按模块或功能分组
- 示例:用户管理、订单管理
Environment 命名规范:
- 项目-环境
- 示例:UserAPI-Dev、UserAPI-Prod
10.5.3 文档维护
markdown
# API 测试文档模板
## 概述
- 测试范围
- 测试环境
- 执行方式
## 前置条件
- 环境变量配置
- 测试数据准备
## 测试用例
| 用例编号 | 用例名称 | 请求方法 | 路径 | 预期结果 |
|---------|---------|---------|------|---------|
| TC001 | 用户登录 | POST | /api/login | 200 |
## 执行命令
\`\`\`bash
newman run collections/api-tests.postman_collection.json -e environments/dev.postman_environment.json
\`\`\`
## 报告查看
- HTML 报告:reports/html/report.html
- JSON 报告:reports/json/report.json