Postman 自动化测试完全指南2

第五章:脚本编程详解

5.1 Pre-request Script 编写

5.1.1 Pre-request Script 概述

Pre-request Script 是在请求发送前执行的脚本,用于动态准备请求数据、设置变量、添加请求头等操作。
服务器 HTTP 请求 Pre-request Script 用户 服务器 HTTP 请求 Pre-request Script 用户 点击发送 执行预处理脚本 设置变量/修改请求 发送处理后的请求 HTTP 请求 HTTP 响应

5.1.2 常用 Pre-request Script 场景

1. 动态生成请求参数

javascript 复制代码
// Pre-request Script 示例:动态生成请求参数

// 生成时间戳和签名
const timestamp = Date.now();
const appId = pm.environment.get('appId');
const appSecret = pm.environment.get('appSecret');

// 生成签名(示例:MD5签名)
const signString = `${appId}${timestamp}${appSecret}`;
const sign = CryptoJS.MD5(signString).toString();

// 设置请求参数
pm.environment.set('timestamp', timestamp);
pm.environment.set('sign', sign);

console.log('生成的签名:', sign);

2. 动态修改请求体

javascript 复制代码
// 动态构建请求体

// 获取基础数据
const userId = pm.environment.get('userId');
const orderId = 'ORD_' + Date.now();

// 构建请求体
const requestBody = {
    orderId: orderId,
    userId: userId,
    items: [
        { productId: 'P001', quantity: 2, price: 99.99 },
        { productId: 'P002', quantity: 1, price: 199.99 }
    ],
    totalAmount: 399.97,
    createTime: new Date().toISOString(),
    paymentMethod: 'alipay'
};

// 设置请求体
pm.request.body.raw = JSON.stringify(requestBody, null, 2);

// 保存订单ID供后续使用
pm.environment.set('currentOrderId', orderId);

console.log('请求体已生成:', pm.request.body.raw);

3. 添加动态请求头

javascript 复制代码
// 添加动态请求头

// 添加追踪ID
pm.request.headers.add({
    key: 'X-Trace-ID',
    value: 'trace_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
});

// 添加时间戳
pm.request.headers.add({
    key: 'X-Timestamp',
    value: Date.now().toString()
});

// 添加认证信息
const token = pm.environment.get('token');
if (token) {
    pm.request.headers.upsert({
        key: 'Authorization',
        value: `Bearer ${token}`
    });
}

// 添加请求来源
pm.request.headers.upsert({
    key: 'X-Request-Source',
    value: 'Postman-AutoTest'
});

console.log('请求头已更新');

4. 条件性请求处理

javascript 复制代码
// 条件性请求处理

const env = pm.environment.name;
const debug = pm.environment.get('debug');

// 根据环境设置不同配置
if (env === 'Production') {
    // 生产环境:使用生产配置
    pm.environment.set('apiVersion', 'v1');
    pm.request.headers.add({
        key: 'X-Environment',
        value: 'production'
    });
} else {
    // 非生产环境:启用调试模式
    pm.environment.set('apiVersion', 'v1');
    pm.request.headers.add({
        key: 'X-Environment',
        value: env.toLowerCase()
    });
    
    if (debug === 'true') {
        pm.request.headers.add({
            key: 'X-Debug-Mode',
            value: 'true'
        });
    }
}

// 检查必要变量
const requiredVars = ['baseUrl', 'token', 'userId'];
requiredVars.forEach(varName => {
    if (!pm.environment.has(varName)) {
        console.error(`缺少必要的环境变量: ${varName}`);
    }
});

5.2 Tests Script 编写

5.2.1 Tests Script 概述

Tests Script 在收到响应后执行,用于验证响应数据、提取数据、设置变量等操作。
Tests Script HTTP 响应 服务器 HTTP 请求 Tests Script HTTP 响应 服务器 HTTP 请求 发送请求 返回响应 执行测试脚本 验证响应 提取数据 设置变量 输出结果

5.2.2 常用 Tests Script 场景

1. 响应数据验证

javascript 复制代码
// Tests Script 示例:响应数据验证

// 验证状态码
pm.test('状态码应为 200', function() {
    pm.response.to.have.status(200);
});

// 验证响应时间
pm.test('响应时间应小于 500ms', function() {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// 验证响应体不为空
pm.test('响应体不应为空', function() {
    pm.response.to.have.jsonBody();
});

// 验证响应结构
pm.test('响应应包含必要字段', function() {
    const response = pm.response.json();
    pm.expect(response).to.have.property('code');
    pm.expect(response).to.have.property('message');
    pm.expect(response).to.have.property('data');
});

// 验证业务状态码
pm.test('业务状态码应为成功', function() {
    const response = pm.response.json();
    pm.expect(response.code).to.equal(200);
});

2. 提取并保存响应数据

javascript 复制代码
// 提取响应数据并保存

const response = pm.response.json();

// 提取并保存 Token
if (response.data && response.data.token) {
    pm.environment.set('token', response.data.token);
    pm.environment.set('tokenExpireTime', response.data.expireTime);
    console.log('Token 已保存');
}

// 提取并保存用户信息
if (response.data && response.data.user) {
    pm.environment.set('userId', response.data.user.id);
    pm.environment.set('username', response.data.user.username);
    pm.environment.set('userRole', response.data.user.role);
    console.log('用户信息已保存');
}

// 提取并保存分页信息
if (response.data && response.data.pagination) {
    pm.environment.set('currentPage', response.data.pagination.page);
    pm.environment.set('totalPages', response.data.pagination.totalPages);
    pm.environment.set('totalCount', response.data.pagination.total);
}

// 提取列表中的第一个元素ID
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
    pm.environment.set('firstItemId', response.data[0].id);
}

3. 复杂数据验证

javascript 复制代码
// 复杂数据验证示例

const response = pm.response.json();

// 验证数组长度
pm.test('返回的用户列表不应为空', function() {
    pm.expect(response.data).to.be.an('array');
    pm.expect(response.data.length).to.be.greaterThan(0);
});

// 验证数组元素结构
pm.test('每个用户应包含必要字段', function() {
    response.data.forEach((user, index) => {
        pm.expect(user).to.have.property('id', `用户${index}缺少id`);
        pm.expect(user).to.have.property('name', `用户${index}缺少name`);
        pm.expect(user).to.have.property('email', `用户${index}缺少email`);
    });
});

// 验证数据类型
pm.test('数据类型应正确', function() {
    response.data.forEach(user => {
        pm.expect(user.id).to.be.a('number');
        pm.expect(user.name).to.be.a('string');
        pm.expect(user.email).to.be.a('string');
        pm.expect(user.age).to.be.a('number');
    });
});

// 验证数值范围
pm.test('用户年龄应在合理范围内', function() {
    response.data.forEach(user => {
        if (user.age) {
            pm.expect(user.age).to.be.within(0, 150);
        }
    });
});

// 验证邮箱格式
pm.test('邮箱格式应正确', function() {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    response.data.forEach(user => {
        pm.expect(user.email).to.match(emailRegex, `${user.email} 不是有效的邮箱格式`);
    });
});

4. 错误处理与日志

javascript 复制代码
// 错误处理与日志记录

try {
    const response = pm.response.json();
    
    // 检查响应状态
    if (pm.response.code !== 200) {
        console.error('请求失败:', {
            status: pm.response.code,
            message: response.message || '未知错误'
        });
    }
    
    // 记录请求信息
    console.log('请求信息:', {
        url: pm.request.url.toString(),
        method: pm.request.method,
        status: pm.response.code,
        time: pm.response.responseTime + 'ms'
    });
    
    // 详细日志(仅在调试模式)
    if (pm.environment.get('debug') === 'true') {
        console.log('请求头:', JSON.stringify(pm.request.headers, null, 2));
        console.log('请求体:', pm.request.body);
        console.log('响应体:', JSON.stringify(response, null, 2));
    }
    
} catch (error) {
    console.error('脚本执行错误:', error.message);
    console.error('原始响应:', pm.response.text());
}

5.3 脚本执行顺序详解

5.3.1 执行顺序规则

Postman 脚本按照特定的顺序执行,理解这个顺序对于编写复杂的测试流程至关重要。
开始执行
Collection Pre-request
Folder Pre-request
Request Pre-request
发送 HTTP 请求
接收响应
Request Tests
Folder Tests
Collection Tests
执行完成

5.3.2 脚本继承与覆盖
javascript 复制代码
// Collection 级别 Pre-request Script
// 位置:Collection -> Edit -> Pre-request Scripts

// 设置公共请求头
pm.request.headers.add({
    key: 'X-API-Version',
    value: 'v1'
});

// 设置公共认证
const token = pm.environment.get('token');
if (token) {
    pm.request.headers.upsert({
        key: 'Authorization',
        value: `Bearer ${token}`
    });
}

console.log('[Collection] 公共预处理完成');
javascript 复制代码
// Folder 级别 Pre-request Script
// 位置:Folder -> Edit -> Pre-request Scripts

// 文件夹级别的特殊处理
console.log('[Folder] 用户模块预处理');

// 检查用户登录状态
if (!pm.environment.has('userId')) {
    console.warn('用户未登录,请先执行登录接口');
}
javascript 复制代码
// Request 级别 Pre-request Script
// 位置:单个请求的 Pre-request Script 标签

// 请求级别的特殊处理
console.log('[Request] 获取用户详情预处理');

// 设置特定的用户ID
const userId = pm.environment.get('userId') || 'default_user';
pm.request.url.path = ['api', 'v1', 'users', userId];
5.3.3 执行顺序实战示例
javascript 复制代码
// 完整的执行顺序演示

// === Collection Pre-request ===
console.log('1. Collection Pre-request 开始');
pm.collectionVariables.set('collectionVar', 'collection_value');

// === Folder Pre-request ===
console.log('2. Folder Pre-request 开始');
pm.environment.set('folderVar', 'folder_value');

// === Request Pre-request ===
console.log('3. Request Pre-request 开始');
pm.variables.set('requestVar', 'request_value');

// === HTTP 请求发送 ===
// 请求发送到服务器

// === Request Tests ===
console.log('4. Request Tests 开始');
pm.test('Request 级别测试', function() {
    pm.response.to.have.status(200);
});

// === Folder Tests ===
console.log('5. Folder Tests 开始');
pm.test('Folder 级别测试', function() {
    const response = pm.response.json();
    pm.expect(response).to.have.property('data');
});

// === Collection Tests ===
console.log('6. Collection Tests 开始');
pm.test('Collection 级别测试', function() {
    pm.expect(pm.response.responseTime).to.be.below(1000);
});

console.log('7. 所有脚本执行完成');

5.4 pm 对象完整 API 详解

5.4.1 pm.request 对象
javascript 复制代码
// pm.request 对象详解

// 获取请求 URL
const url = pm.request.url;
console.log('完整URL:', url.toString());
console.log('协议:', url.protocol);
console.log('主机:', url.host);
console.log('端口:', url.port);
console.log('路径:', url.path);
console.log('查询参数:', url.query);

// 获取请求方法
console.log('请求方法:', pm.request.method);

// 操作请求头
// 添加请求头
pm.request.headers.add({
    key: 'X-Custom-Header',
    value: 'custom-value'
});

// 更新或添加请求头
pm.request.headers.upsert({
    key: 'X-Custom-Header',
    value: 'updated-value'
});

// 删除请求头
pm.request.headers.remove('X-Custom-Header');

// 获取请求头
const authHeader = pm.request.headers.get('Authorization');
console.log('认证头:', authHeader);

// 遍历所有请求头
pm.request.headers.each(header => {
    console.log(`请求头 ${header.key}: ${header.value}`);
});

// 操作请求体
console.log('请求体类型:', pm.request.body.mode);

// 修改 JSON 请求体
if (pm.request.body.mode === 'raw') {
    const body = JSON.parse(pm.request.body.raw);
    body.timestamp = Date.now();
    pm.request.body.raw = JSON.stringify(body);
}
5.4.2 pm.response 对象
javascript 复制代码
// pm.response 对象详解

// 基本属性
console.log('状态码:', pm.response.code);
console.log('状态文本:', pm.response.status);
console.log('响应时间:', pm.response.responseTime, 'ms');

// 获取响应体
const jsonResponse = pm.response.json();  // JSON 格式
const textResponse = pm.response.text();  // 文本格式
const rawResponse = pm.response.stream;   // 原始数据(Buffer)

// 获取响应大小
const size = pm.response.size();
console.log('响应大小:', {
    total: size.total,      // 总大小
    headers: size.headers,  // 响应头大小
    body: size.body         // 响应体大小
});

// 获取响应头
const contentType = pm.response.headers.get('Content-Type');
console.log('内容类型:', contentType);

// 遍历所有响应头
pm.response.headers.each(header => {
    console.log(`响应头 ${header.key}: ${header.value}`);
});

// 检查响应头是否存在
if (pm.response.headers.has('X-RateLimit-Remaining')) {
    const remaining = pm.response.headers.get('X-RateLimit-Remaining');
    console.log('剩余请求次数:', remaining);
}
5.4.3 pm.info 对象
javascript 复制代码
// pm.info 对象详解

// 获取当前迭代信息
console.log('当前迭代索引:', pm.info.iteration);
console.log('总迭代次数:', pm.info.iterationCount);

// 获取请求信息
console.log('请求名称:', pm.info.requestName);
console.log('请求ID:', pm.info.requestId);

// 使用迭代信息实现条件逻辑
if (pm.info.iteration === 0) {
    console.log('第一次迭代,执行初始化操作');
}

// 计算进度
const progress = ((pm.info.iteration + 1) / pm.info.iterationCount * 100).toFixed(2);
console.log(`执行进度: ${progress}%`);
5.4.4 pm.sendRequest 方法
javascript 复制代码
// pm.sendRequest 方法详解

// 基本用法
pm.sendRequest('https://api.example.com/health', (error, response) => {
    if (error) {
        console.error('请求失败:', error);
        return;
    }
    console.log('健康检查结果:', response.json());
});

// 完整配置
pm.sendRequest({
    url: pm.environment.get('baseUrl') + '/api/v1/validate',
    method: 'POST',
    header: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + pm.environment.get('token')
    },
    body: {
        mode: 'raw',
        raw: JSON.stringify({
            token: pm.environment.get('token'),
            timestamp: Date.now()
        })
    },
    timeout: 5000
}, (error, response) => {
    if (error) {
        console.error('验证请求失败:', error);
        return;
    }
    
    const result = response.json();
    if (result.valid) {
        console.log('Token 有效');
    } else {
        console.log('Token 已过期,需要重新登录');
    }
});

// 使用 Promise 方式(异步)
async function fetchData() {
    return new Promise((resolve, reject) => {
        pm.sendRequest({
            url: pm.environment.get('baseUrl') + '/api/v1/data',
            method: 'GET'
        }, (error, response) => {
            if (error) {
                reject(error);
            } else {
                resolve(response.json());
            }
        });
    });
}

// 在支持 async/await 的环境中使用
// const data = await fetchData();

5.5 数据提取与链式传递

5.5.1 JSONPath 数据提取
javascript 复制代码
// JSONPath 数据提取示例

const response = pm.response.json();

// 基本路径提取
const userId = response.data.id;
const userName = response.data.name;
const userEmail = response.data.email;

// 嵌套对象提取
const street = response.data.address.street;
const city = response.data.address.city;

// 数组提取
const firstItem = response.data.items[0];
const allItemNames = response.data.items.map(item => item.name);

// 条件提取
const expensiveItems = response.data.items.filter(item => item.price > 100);

// 复杂提取
const totalPrice = response.data.items.reduce((sum, item) => {
    return sum + (item.price * item.quantity);
}, 0);

console.log('总价:', totalPrice);

// 保存提取的数据
pm.environment.set('userId', userId);
pm.environment.set('firstItemId', firstItem.id);
pm.environment.set('totalPrice', totalPrice);
5.5.2 链式传递实现

提取Token
提取UserId
提取OrderId
验证数据
登录接口
获取用户信息
获取订单列表
获取订单详情
测试完成

javascript 复制代码
// 链式传递完整示例

// === 请求1:登录 ===
// Tests Script
const loginResponse = pm.response.json();
if (loginResponse.code === 200) {
    // 提取并保存 Token
    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('userName', userResponse.data.name);
    pm.environment.set('userEmail', userResponse.data.email);
    console.log('用户信息已获取');
}

// === 请求3:获取订单列表 ===
// Pre-request Script
pm.request.headers.upsert({
    key: 'Authorization',
    value: 'Bearer ' + pm.environment.get('token')
});
// 使用用户ID作为查询参数
pm.request.url.query.add({
    key: 'userId',
    value: pm.environment.get('userId')
});

// Tests Script
const ordersResponse = pm.response.json();
if (ordersResponse.code === 200 && ordersResponse.data.length > 0) {
    // 提取第一个订单ID
    pm.environment.set('orderId', ordersResponse.data[0].id);
    console.log('订单列表已获取,第一个订单ID:', ordersResponse.data[0].id);
}

// === 请求4:获取订单详情 ===
// Pre-request Script
pm.request.headers.upsert({
    key: 'Authorization',
    value: 'Bearer ' + pm.environment.get('token')
});
// 设置路径参数
const orderId = pm.environment.get('orderId');
pm.request.url.path = ['api', 'v1', 'orders', orderId];

// Tests Script
const orderDetail = pm.response.json();
pm.test('订单详情应正确', function() {
    pm.expect(orderDetail.data.id).to.equal(pm.environment.get('orderId'));
    pm.expect(orderDetail.data.items).to.be.an('array');
});

5.6 流程控制

5.6.1 postman.setNextRequest 方法

postman.setNextRequest() 方法用于控制请求的执行顺序,可以实现条件跳转和循环。

javascript 复制代码
// 流程控制示例

const response = pm.response.json();

// 条件跳转
if (response.code === 200) {
    // 成功则执行下一个请求
    postman.setNextRequest('获取用户信息');
} else if (response.code === 401) {
    // Token 过期,重新登录
    console.log('Token 已过期,重新登录');
    postman.setNextRequest('登录');
} else {
    // 其他错误,停止执行
    console.error('请求失败,停止执行');
    postman.setNextRequest(null);
}

// 循环控制示例
const currentPage = parseInt(pm.environment.get('currentPage') || '1');
const totalPages = parseInt(pm.environment.get('totalPages') || '1');

if (currentPage < totalPages) {
    // 还有更多页面,继续获取
    pm.environment.set('currentPage', currentPage + 1);
    postman.setNextRequest('获取用户列表');
} else {
    // 所有页面获取完成
    console.log('所有页面数据获取完成');
    pm.environment.set('currentPage', '1');  // 重置
    postman.setNextRequest(null);  // 停止
}
5.6.2 复杂流程控制









开始: 登录
登录成功?
获取用户信息
记录错误并停止
用户状态正常?
获取订单列表
激活用户
有订单?
处理订单
创建测试订单
还有更多订单?
结束

javascript 复制代码
// 复杂流程控制实现

// === 登录请求 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);
    postman.setNextRequest('获取用户信息');
} else {
    console.error('登录失败:', loginResponse.message);
    postman.setNextRequest(null);
}

// === 获取用户信息 Tests Script ===
const userResponse = pm.response.json();

if (userResponse.code === 200) {
    if (userResponse.data.status === 'active') {
        postman.setNextRequest('获取订单列表');
    } else {
        console.log('用户状态异常,需要激活');
        postman.setNextRequest('激活用户');
    }
} else {
    console.error('获取用户信息失败');
    postman.setNextRequest(null);
}

// === 获取订单列表 Tests Script ===
const ordersResponse = pm.response.json();

if (ordersResponse.code === 200) {
    if (ordersResponse.data.length > 0) {
        pm.environment.set('orderIndex', 0);
        pm.environment.set('orders', JSON.stringify(ordersResponse.data));
        postman.setNextRequest('处理订单');
    } else {
        console.log('无订单,创建测试订单');
        postman.setNextRequest('创建测试订单');
    }
}

// === 处理订单 Tests Script ===
const orders = JSON.parse(pm.environment.get('orders'));
const currentIndex = parseInt(pm.environment.get('orderIndex'));

// 处理当前订单
console.log(`处理订单 ${currentIndex + 1}/${orders.length}:`, orders[currentIndex].id);

if (currentIndex + 1 < orders.length) {
    // 还有更多订单
    pm.environment.set('orderIndex', currentIndex + 1);
    postman.setNextRequest('处理订单');
} else {
    // 所有订单处理完成
    console.log('所有订单处理完成');
    postman.setNextRequest(null);
}

5.7 错误处理

5.7.1 try-catch 异常捕获
javascript 复制代码
// 错误处理示例

// 基本错误处理
try {
    const response = pm.response.json();
    
    // 验证响应结构
    if (!response.data) {
        throw new Error('响应数据格式错误:缺少 data 字段');
    }
    
    // 处理数据
    const userId = response.data.id;
    pm.environment.set('userId', userId);
    
} catch (error) {
    console.error('处理响应时发生错误:', error.message);
    console.error('原始响应:', pm.response.text());
    
    // 标记测试失败
    pm.test('响应处理', function() {
        pm.expect.fail('响应处理失败: ' + error.message);
    });
}

// 多层错误处理
try {
    const response = pm.response.json();
    
    try {
        // 尝试提取嵌套数据
        const nestedData = response.data.nested.deep.value;
        pm.environment.set('nestedValue', nestedData);
    } catch (nestedError) {
        console.warn('嵌套数据不存在,使用默认值');
        pm.environment.set('nestedValue', 'default');
    }
    
} catch (error) {
    console.error('JSON 解析失败:', error.message);
}
5.7.2 错误恢复策略
javascript 复制代码
// 错误恢复策略

// 1. 重试机制
async function retryRequest(config, maxRetries = 3) {
    let lastError;
    
    for (let i = 0; i < maxRetries; i++) {
        try {
            const result = await new Promise((resolve, reject) => {
                pm.sendRequest(config, (error, response) => {
                    if (error) {
                        reject(error);
                    } else {
                        resolve(response);
                    }
                });
            });
            
            if (result.code === 200) {
                return result;
            }
        } catch (error) {
            lastError = error;
            console.log(`第 ${i + 1} 次尝试失败:`, error.message);
        }
        
        // 等待一段时间后重试
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
    
    throw lastError || new Error('重试次数已用尽');
}

// 2. 降级处理
function getDataWithFallback() {
    try {
        const response = pm.response.json();
        if (response.code === 200) {
            return response.data;
        }
    } catch (error) {
        console.warn('主数据源获取失败,使用备用数据');
    }
    
    // 使用缓存或默认数据
    const cachedData = pm.environment.get('cachedData');
    if (cachedData) {
        return JSON.parse(cachedData);
    }
    
    return { default: true };
}

// 3. 错误上报
function reportError(error, context) {
    const errorInfo = {
        timestamp: new Date().toISOString(),
        request: {
            url: pm.request.url.toString(),
            method: pm.request.method
        },
        response: {
            status: pm.response.code,
            body: pm.response.text()
        },
        error: error.message,
        context: context
    };
    
    console.error('错误详情:', JSON.stringify(errorInfo, null, 2));
    
    // 发送错误到监控系统
    pm.sendRequest({
        url: pm.environment.get('baseUrl') + '/api/errors/report',
        method: 'POST',
        header: { 'Content-Type': 'application/json' },
        body: { mode: 'raw', raw: JSON.stringify(errorInfo) }
    }, (err) => {
        if (err) {
            console.error('错误上报失败:', err.message);
        }
    });
}

5.8 异步处理

5.8.1 setTimeout 和 Promise
javascript 复制代码
// 异步处理示例

// 使用 setTimeout 延迟执行
setTimeout(function() {
    console.log('延迟 2 秒后执行');
}, 2000);

// 使用 Promise 封装异步操作
function waitForCondition(condition, timeout = 5000, interval = 500) {
    return new Promise((resolve, reject) => {
        const startTime = Date.now();
        
        const check = () => {
            if (condition()) {
                resolve(true);
                return;
            }
            
            if (Date.now() - startTime > timeout) {
                reject(new Error('等待超时'));
                return;
            }
            
            setTimeout(check, interval);
        };
        
        check();
    });
}

// 使用示例
waitForCondition(() => pm.environment.has('token'))
    .then(() => {
        console.log('Token 已就绪');
    })
    .catch(error => {
        console.error('等待 Token 超时');
    });
5.8.2 async/await 使用
javascript 复制代码
// async/await 示例

// 封装 sendRequest 为 Promise
function sendRequestAsync(config) {
    return new Promise((resolve, reject) => {
        pm.sendRequest(config, (error, response) => {
            if (error) {
                reject(error);
            } else {
                resolve(response);
            }
        });
    });
}

// 使用 async/await 编写顺序执行的测试
(async function() {
    try {
        // 步骤1:验证 Token
        const validateResponse = await sendRequestAsync({
            url: pm.environment.get('baseUrl') + '/api/v1/auth/validate',
            method: 'POST',
            header: {
                'Authorization': 'Bearer ' + pm.environment.get('token')
            }
        });
        
        if (validateResponse.json().code !== 200) {
            throw new Error('Token 验证失败');
        }
        
        // 步骤2:获取用户数据
        const userResponse = await sendRequestAsync({
            url: pm.environment.get('baseUrl') + '/api/v1/users/me',
            method: 'GET',
            header: {
                'Authorization': 'Bearer ' + pm.environment.get('token')
            }
        });
        
        const userData = userResponse.json().data;
        pm.environment.set('userId', userData.id);
        
        // 步骤3:获取用户订单
        const ordersResponse = await sendRequestAsync({
            url: pm.environment.get('baseUrl') + '/api/v1/orders',
            method: 'GET',
            header: {
                'Authorization': 'Bearer ' + pm.environment.get('token')
            }
        });
        
        const orders = ordersResponse.json().data;
        console.log('用户订单数量:', orders.length);
        
        // 验证结果
        pm.test('异步流程测试', function() {
            pm.expect(userData).to.have.property('id');
            pm.expect(orders).to.be.an('array');
        });
        
    } catch (error) {
        console.error('异步流程执行失败:', error.message);
        pm.test('异步流程测试', function() {
            pm.expect.fail(error.message);
        });
    }
})();

5.9 外部库使用

5.9.1 内置外部库

Postman 内置了多个常用的 JavaScript 库:

库名 用途 示例
CryptoJS 加密解密 CryptoJS.MD5('text')
Lodash 工具函数 _.groupBy(array, 'key')
Moment 日期处理 moment().format('YYYY-MM-DD')
Cheerio HTML 解析 cheerio.load(html)
uuid UUID 生成 uuid.v4()
5.9.2 CryptoJS 加密库
javascript 复制代码
// CryptoJS 使用示例

// MD5 加密
const md5Hash = CryptoJS.MD5('password123').toString();
console.log('MD5:', md5Hash);

// SHA256 加密
const sha256Hash = CryptoJS.SHA256('password123').toString();
console.log('SHA256:', sha256Hash);

// HMAC-SHA256 签名
const secret = 'my-secret-key';
const message = 'data to sign';
const hmacSha256 = CryptoJS.HmacSHA256(message, secret).toString();
console.log('HMAC-SHA256:', hmacSha256);

// AES 加密
const key = '1234567890123456';  // 16位密钥
const encrypted = CryptoJS.AES.encrypt('敏感数据', key).toString();
console.log('AES 加密:', encrypted);

// AES 解密
const decrypted = CryptoJS.AES.decrypt(encrypted, key).toString(CryptoJS.enc.Utf8);
console.log('AES 解密:', decrypted);

// Base64 编码
const base64Encoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse('Hello World'));
console.log('Base64 编码:', base64Encoded);

// Base64 解码
const base64Decoded = CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(base64Encoded));
console.log('Base64 解码:', base64Decoded);

// 实际应用:生成 API 签名
function generateApiSignature(params, appSecret) {
    // 按键名排序
    const sortedKeys = Object.keys(params).sort();
    
    // 拼接字符串
    const signString = sortedKeys.map(key => `${key}=${params[key]}`).join('&') + appSecret;
    
    // 计算签名
    return CryptoJS.MD5(signString).toString().toUpperCase();
}

const params = {
    appId: '12345',
    timestamp: Date.now(),
    userId: 'user001'
};
const signature = generateApiSignature(params, 'app_secret_key');
console.log('API 签名:', signature);
5.9.3 Lodash 工具库
javascript 复制代码
// Lodash 使用示例

const data = pm.response.json().data;

// 分组
const groupedByStatus = _.groupBy(data, 'status');
console.log('按状态分组:', groupedByStatus);

// 排序
const sorted = _.orderBy(data, ['createdAt'], ['desc']);
console.log('按创建时间降序:', sorted);

// 过滤
const activeUsers = _.filter(data, { status: 'active' });
console.log('活跃用户:', activeUsers);

// 查找
const user = _.find(data, { id: 123 });
console.log('ID为123的用户:', user);

// 去重
const uniqueEmails = _.uniq(_.map(data, 'email'));
console.log('唯一邮箱:', uniqueEmails);

// 深拷贝
const deepCopy = _.cloneDeep(data);
console.log('深拷贝:', deepCopy);

// 合并对象
const merged = _.merge({}, { a: 1 }, { b: 2 }, { a: 3 });
console.log('合并对象:', merged);

// 获取嵌套属性(安全访问)
const nestedValue = _.get(data, 'user.profile.avatar', 'default-avatar.png');
console.log('嵌套属性:', nestedValue);

// 设置嵌套属性
const obj = {};
_.set(obj, 'user.profile.name', '张三');
console.log('设置嵌套属性:', obj);
5.9.4 Moment 日期库
javascript 复制代码
// Moment 使用示例

// 当前时间
const now = moment();
console.log('当前时间:', now.format('YYYY-MM-DD HH:mm:ss'));

// 时间戳转换
const timestamp = moment().valueOf();
console.log('时间戳:', timestamp);

const fromTimestamp = moment(1705312800000);
console.log('时间戳转换:', fromTimestamp.format('YYYY-MM-DD'));

// 字符串解析
const parsed = moment('2024-01-15', 'YYYY-MM-DD');
console.log('解析日期:', parsed.format('YYYY年MM月DD日'));

// 时间计算
const tomorrow = moment().add(1, 'days');
const lastMonth = moment().subtract(1, 'months');
const endOfMonth = moment().endOf('month');

console.log('明天:', tomorrow.format('YYYY-MM-DD'));
console.log('上个月:', lastMonth.format('YYYY-MM'));
console.log('月末:', endOfMonth.format('YYYY-MM-DD'));

// 时间差
const start = moment('2024-01-01');
const end = moment('2024-01-15');
const diff = end.diff(start, 'days');
console.log('相差天数:', diff);

// 格式化
console.log('完整格式:', moment().format('YYYY年MM月DD日 HH时mm分ss秒'));
console.log('ISO格式:', moment().toISOString());
console.log('相对时间:', moment().subtract(2, 'hours').fromNow());

5.10 Postman Console 调试技巧

5.10.1 Console 基本使用
javascript 复制代码
// Console 调试示例

// 基本日志
console.log('普通日志');
console.info('信息日志');
console.warn('警告日志');
console.error('错误日志');

// 打印对象
const response = pm.response.json();
console.log('响应对象:', response);
console.log('格式化JSON:', JSON.stringify(response, null, 2));

// 打印表格
const users = [
    { id: 1, name: '张三', age: 25 },
    { id: 2, name: '李四', age: 30 },
    { id: 3, name: '王五', age: 28 }
];
console.table(users);

// 分组日志
console.group('请求详情');
console.log('URL:', pm.request.url.toString());
console.log('方法:', pm.request.method);
console.log('状态码:', pm.response.code);
console.log('响应时间:', pm.response.responseTime + 'ms');
console.groupEnd();

// 计时
console.time('数据处理');
// ... 数据处理代码
console.timeEnd('数据处理');

// 断言
console.assert(pm.response.code === 200, '状态码不是200');
console.assert(response.data !== undefined, '响应数据为空');
5.10.2 调试最佳实践
javascript 复制代码
// 调试最佳实践

// 1. 使用调试标志
const DEBUG = pm.environment.get('debug') === 'true';

function debugLog(message, data) {
    if (DEBUG) {
        console.log(`[DEBUG] ${message}:`, data);
    }
}

debugLog('请求体', pm.request.body);
debugLog('响应体', pm.response.json());

// 2. 结构化日志
function logRequest() {
    console.log('=== 请求信息 ===');
    console.log('时间:', new Date().toISOString());
    console.log('URL:', pm.request.url.toString());
    console.log('方法:', pm.request.method);
    console.log('请求头:', pm.request.headers.toObject());
    console.log('请求体:', pm.request.body);
}

function logResponse() {
    console.log('=== 响应信息 ===');
    console.log('状态码:', pm.response.code);
    console.log('响应时间:', pm.response.responseTime + 'ms');
    console.log('响应大小:', pm.response.size());
    console.log('响应体:', pm.response.json());
}

// 3. 条件断点
const response = pm.response.json();
if (response.code !== 200) {
    console.warn('=== 错误响应详情 ===');
    console.warn('状态码:', pm.response.code);
    console.warn('错误信息:', response.message);
    console.warn('请求详情:', {
        url: pm.request.url.toString(),
        method: pm.request.method,
        body: pm.request.body
    });
}

// 4. 性能分析
const startTime = Date.now();

// ... 执行代码

const endTime = Date.now();
console.log(`执行耗时: ${endTime - startTime}ms`);

// 5. 变量状态检查
function checkVariables() {
    console.log('=== 变量状态 ===');
    console.log('环境变量:', pm.environment.toObject());
    console.log('全局变量:', pm.globals.toObject());
    console.log('集合变量:', pm.collectionVariables.toObject());
}

checkVariables();

第六章:断言机制与响应验证

6.1 pm.test 断言框架

6.1.1 pm.test 基本语法

pm.test() 是 Postman 提供的测试断言框架,用于编写和组织测试用例。

javascript 复制代码
// pm.test 基本语法
pm.test(testName, function() {
    // 断言逻辑
});

// 示例
pm.test('状态码应为 200', function() {
    pm.response.to.have.status(200);
});
6.1.2 测试结果分类

测试结果
通过 Pass
失败 Fail
跳过 Skip
断言条件满足
断言条件不满足
条件跳过测试

测试跳过:

javascript 复制代码
// 条件跳过测试
const skipLongTests = pm.environment.get('skipLongTests') === 'true';

if (skipLongTests) {
    pm.test.skip('跳过长时间测试', function() {
        // 此测试将被跳过
    });
} else {
    pm.test('长时间测试', function() {
        // 执行测试
    });
}

6.2 pm.expect 断言方法

6.2.1 pm.expect 基本用法

pm.expect() 基于 Chai 断言库,提供了丰富的断言方法。

javascript 复制代码
// pm.expect 基本用法

const response = pm.response.json();

// 相等断言
pm.expect(response.code).to.equal(200);
pm.expect(response.message).to.eql('成功');

// 布尔断言
pm.expect(response.success).to.be.true;
pm.expect(response.error).to.be.false;
pm.expect(response.data).to.exist;

// 类型断言
pm.expect(response.data).to.be.an('object');
pm.expect(response.data.name).to.be.a('string');
pm.expect(response.data.age).to.be.a('number');
pm.expect(response.data.tags).to.be.an('array');

// 包含断言
pm.expect(response.data.name).to.include('张');
pm.expect(response.data.tags).to.include('VIP');
pm.expect(response.data).to.have.property('id');
6.2.2 常用断言方法详解

1. 相等性断言

javascript 复制代码
// 相等性断言示例

const data = pm.response.json().data;

// 严格相等(===)
pm.expect(data.id).to.equal(123);

// 深度相等(对象比较)
pm.expect(data.profile).to.eql({
    name: '张三',
    age: 25
});

// 大于/小于
pm.expect(data.age).to.be.above(18);      // 大于
pm.expect(data.age).to.be.below(60);      // 小于
pm.expect(data.score).to.be.at.least(60); // 至少
pm.expect(data.score).to.be.at.most(100); // 最多

// 范围断言
pm.expect(data.age).to.be.within(18, 60);

2. 类型断言

javascript 复制代码
// 类型断言示例

const data = pm.response.json().data;

// 基本类型
pm.expect(data.name).to.be.a('string');
pm.expect(data.age).to.be.a('number');
pm.expect(data.active).to.be.a('boolean');

// 复杂类型
pm.expect(data).to.be.an('object');
pm.expect(data.tags).to.be.an('array');
pm.expect(data.callback).to.be.a('function');

// 特殊值检查
pm.expect(data.nullValue).to.be.null;
pm.expect(data.undefinedValue).to.be.undefined;
pm.expect(data.naNValue).to.be.NaN;

3. 包含断言

javascript 复制代码
// 包含断言示例

const data = pm.response.json().data;

// 字符串包含
pm.expect(data.name).to.include('张');
pm.expect(data.email).to.contain('@');

// 数组包含
pm.expect(data.tags).to.include('VIP');
pm.expect(data.tags).to.include.members(['VIP', '活跃']);

// 对象包含属性
pm.expect(data).to.have.property('id');
pm.expect(data).to.have.property('name', '张三');
pm.expect(data).to.have.all.keys('id', 'name', 'email');

// 深度包含
pm.expect(data).to.deep.include({
    name: '张三',
    age: 25
});

4. 布尔断言

javascript 复制代码
// 布尔断言示例

const data = pm.response.json().data;

// 真值检查
pm.expect(data.active).to.be.true;
pm.expect(data.deleted).to.be.false;

// 存在性检查
pm.expect(data.id).to.exist;
pm.expect(data.optional).to.not.exist;

// 空值检查
pm.expect(data.name).to.not.be.empty;
pm.expect(data.tags).to.not.be.empty;

// 可迭代对象检查
pm.expect(data.tags).to.be.an('array').that.is.not.empty;

5. 字符串断言

javascript 复制代码
// 字符串断言示例

const data = pm.response.json().data;

// 字符串匹配
pm.expect(data.name).to.match(/^张/);          // 正则匹配
pm.expect(data.email).to.match(/@.*\.com$/);

// 字符串长度
pm.expect(data.name).to.have.lengthOf(2);
pm.expect(data.description).to.have.length.above(10);
pm.expect(data.code).to.have.length.within(4, 6);

// 字符串开头/结尾
pm.expect(data.name).to.startWith('张');
pm.expect(data.email).to.endWith('.com');

6. 数组断言

javascript 复制代码
// 数组断言示例

const data = pm.response.json().data;

// 数组长度
pm.expect(data.items).to.have.lengthOf(5);
pm.expect(data.items).to.have.length.above(0);
pm.expect(data.items).to.have.length.below(10);

// 数组元素
pm.expect(data.items).to.include('item1');
pm.expect(data.items[0]).to.have.property('id');

// 数组排序
pm.expect(data.sortedNumbers).to.be.sorted();
pm.expect(data.sortedNumbers).to.be.ascending;
pm.expect(data.sortedNumbers).to.be.descending;

// 数组唯一性
pm.expect(data.uniqueIds).to.have.uniqueValues();

6.3 响应状态码验证

6.3.1 状态码断言方法
javascript 复制代码
// 状态码断言示例

// 1. 精确状态码
pm.test('状态码应为 200', function() {
    pm.response.to.have.status(200);
});

// 2. 状态码范围
pm.test('状态码应为 2xx', function() {
    pm.response.to.be.success;  // 200-299
});

pm.test('状态码应为 4xx', function() {
    pm.response.to.be.clientError;  // 400-499
});

pm.test('状态码应为 5xx', function() {
    pm.response.to.be.serverError;  // 500-599
});

// 3. 特定状态类型
pm.test('应为成功响应', function() {
    pm.response.to.be.ok;  // 200
});

pm.test('应为创建成功', function() {
    pm.response.to.be.created;  // 201
});

pm.test('应为无内容', function() {
    pm.response.to.be.noContent;  // 204
});

pm.test('应为错误请求', function() {
    pm.response.to.be.badRequest;  // 400
});

pm.test('应为未授权', function() {
    pm.response.to.be.unauthorized;  // 401
});

pm.test('应为禁止访问', function() {
    pm.response.to.be.forbidden;  // 403
});

pm.test('应为未找到', function() {
    pm.response.to.be.notFound;  // 404
});
6.3.2 状态码验证最佳实践
javascript 复制代码
// 状态码验证最佳实践

const statusCode = pm.response.code;

// 根据状态码执行不同验证
switch (statusCode) {
    case 200:
        pm.test('成功响应应包含数据', function() {
            const response = pm.response.json();
            pm.expect(response.data).to.exist;
        });
        break;
        
    case 201:
        pm.test('创建成功应返回资源ID', function() {
            const response = pm.response.json();
            pm.expect(response.data.id).to.exist;
            
            // 验证 Location 头
            const location = pm.response.headers.get('Location');
            pm.expect(location).to.include('/api/');
        });
        break;
        
    case 400:
        pm.test('错误请求应返回错误信息', function() {
            const response = pm.response.json();
            pm.expect(response.message).to.exist;
            pm.expect(response.errors).to.be.an('array');
        });
        break;
        
    case 401:
        pm.test('未授权应返回认证信息', function() {
            const wwwAuthenticate = pm.response.headers.get('WWW-Authenticate');
            pm.expect(wwwAuthenticate).to.exist;
        });
        break;
        
    case 404:
        pm.test('未找到应返回错误信息', function() {
            const response = pm.response.json();
            pm.expect(response.message).to.include('not found');
        });
        break;
        
    case 500:
        pm.test('服务器错误应记录详情', function() {
            console.error('服务器错误:', pm.response.text());
        });
        break;
        
    default:
        pm.test(`未处理的状态码: ${statusCode}`, function() {
            console.log('响应:', pm.response.text());
        });
}

6.4 响应体验证

6.4.1 JSON 响应验证
javascript 复制代码
// JSON 响应验证示例

const response = pm.response.json();

// 1. 验证响应结构
pm.test('响应应包含标准结构', function() {
    pm.expect(response).to.have.property('code');
    pm.expect(response).to.have.property('message');
    pm.expect(response).to.have.property('data');
});

// 2. 验证业务状态码
pm.test('业务状态码应为成功', function() {
    pm.expect(response.code).to.equal(200);
});

// 3. 验证数据字段
pm.test('用户数据应包含必要字段', function() {
    const user = response.data;
    pm.expect(user).to.have.property('id');
    pm.expect(user).to.have.property('name');
    pm.expect(user).to.have.property('email');
    pm.expect(user).to.have.property('createdAt');
});

// 4. 验证数据类型
pm.test('数据类型应正确', function() {
    const user = response.data;
    pm.expect(user.id).to.be.a('number');
    pm.expect(user.name).to.be.a('string');
    pm.expect(user.email).to.be.a('string');
    pm.expect(user.createdAt).to.be.a('string');
});

// 5. 验证数据格式
pm.test('邮箱格式应正确', function() {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    pm.expect(response.data.email).to.match(emailRegex);
});

pm.test('日期格式应为 ISO 8601', function() {
    const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
    pm.expect(response.data.createdAt).to.match(dateRegex);
});

// 6. 验证嵌套对象
pm.test('地址信息应完整', function() {
    const address = response.data.address;
    pm.expect(address).to.be.an('object');
    pm.expect(address).to.have.property('province');
    pm.expect(address).to.have.property('city');
    pm.expect(address).to.have.property('detail');
});

// 7. 验证数组数据
pm.test('订单列表应有效', function() {
    const orders = response.data.orders;
    pm.expect(orders).to.be.an('array');
    
    orders.forEach((order, index) => {
        pm.test(`订单 ${index + 1} 应包含必要字段`, function() {
            pm.expect(order).to.have.property('id');
            pm.expect(order).to.have.property('amount');
            pm.expect(order.amount).to.be.above(0);
        });
    });
});
6.4.2 分页数据验证
javascript 复制代码
// 分页数据验证示例

const response = pm.response.json();

pm.test('分页数据结构应正确', function() {
    // 验证数据列表
    pm.expect(response.data).to.be.an('array');
    
    // 验证分页信息
    pm.expect(response.pagination).to.be.an('object');
    pm.expect(response.pagination).to.have.property('page');
    pm.expect(response.pagination).to.have.property('size');
    pm.expect(response.pagination).to.have.property('total');
    pm.expect(response.pagination).to.have.property('totalPages');
});

pm.test('分页参数应有效', function() {
    const pagination = response.pagination;
    
    // 页码从1开始
    pm.expect(pagination.page).to.be.at.least(1);
    
    // 每页数量合理
    pm.expect(pagination.size).to.be.within(1, 100);
    
    // 总数非负
    pm.expect(pagination.total).to.be.at.least(0);
    
    // 总页数计算正确
    const expectedTotalPages = Math.ceil(pagination.total / pagination.size);
    pm.expect(pagination.totalPages).to.equal(expectedTotalPages);
});

pm.test('当前页数据数量应正确', function() {
    const pagination = response.pagination;
    const dataCount = response.data.length;
    
    if (pagination.page < pagination.totalPages) {
        // 非最后一页,数据量应等于 size
        pm.expect(dataCount).to.equal(pagination.size);
    } else if (pagination.page === pagination.totalPages) {
        // 最后一页,数据量应小于等于 size
        pm.expect(dataCount).to.be.at.most(pagination.size);
    }
});

6.5 响应头验证

6.5.1 常用响应头验证
javascript 复制代码
// 响应头验证示例

// 1. Content-Type 验证
pm.test('Content-Type 应为 JSON', function() {
    pm.response.to.have.header('Content-Type');
    const contentType = pm.response.headers.get('Content-Type');
    pm.expect(contentType).to.include('application/json');
});

// 2. 响应时间验证
pm.test('响应时间应小于 500ms', function() {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// 3. 服务器信息验证
pm.test('应包含服务器信息', function() {
    const server = pm.response.headers.get('Server');
    pm.expect(server).to.exist;
});

// 4. 缓存控制验证
pm.test('应包含缓存控制头', function() {
    const cacheControl = pm.response.headers.get('Cache-Control');
    if (cacheControl) {
        pm.expect(cacheControl).to.match(/(no-cache|no-store|max-age)/);
    }
});

// 5. CORS 头验证
pm.test('应包含 CORS 头', function() {
    const allowOrigin = pm.response.headers.get('Access-Control-Allow-Origin');
    const allowMethods = pm.response.headers.get('Access-Control-Allow-Methods');
    
    pm.expect(allowOrigin).to.exist;
    pm.expect(allowMethods).to.exist;
});

// 6. 限流头验证
pm.test('应包含限流信息', function() {
    const rateLimit = pm.response.headers.get('X-RateLimit-Limit');
    const rateRemaining = pm.response.headers.get('X-RateLimit-Remaining');
    const rateReset = pm.response.headers.get('X-RateLimit-Reset');
    
    if (rateLimit) {
        pm.expect(parseInt(rateLimit)).to.be.above(0);
        pm.expect(parseInt(rateRemaining)).to.be.at.least(0);
        pm.expect(parseInt(rateReset)).to.be.above(0);
        
        console.log(`限流信息: 剩余 ${rateRemaining}/${rateLimit}, 重置时间: ${rateReset}`);
    }
});

// 7. 请求追踪验证
pm.test('应包含请求追踪ID', function() {
    const traceId = pm.response.headers.get('X-Trace-ID') || 
                    pm.response.headers.get('X-Request-ID');
    
    if (traceId) {
        pm.expect(traceId).to.be.a('string');
        pm.environment.set('lastTraceId', traceId);
    }
});
javascript 复制代码
// Set-Cookie 验证示例

pm.test('应设置正确的 Cookie', function() {
    const setCookie = pm.response.headers.get('Set-Cookie');
    
    if (setCookie) {
        // 解析 Cookie 属性
        const cookieParts = setCookie.split(';').map(s => s.trim());
        const cookieName = cookieParts[0].split('=')[0];
        const cookieValue = cookieParts[0].split('=')[1];
        
        // 验证 Cookie 名称
        pm.expect(cookieName).to.exist;
        
        // 验证安全属性
        const hasHttpOnly = cookieParts.some(p => p.toLowerCase() === 'httponly');
        const hasSecure = cookieParts.some(p => p.toLowerCase() === 'secure');
        const hasSameSite = cookieParts.some(p => p.toLowerCase().startsWith('samesite'));
        
        pm.test('Cookie 应设置 HttpOnly', function() {
            pm.expect(hasHttpOnly).to.be.true;
        });
        
        pm.test('Cookie 应设置 Secure', function() {
            pm.expect(hasSecure).to.be.true;
        });
        
        pm.test('Cookie 应设置 SameSite', function() {
            pm.expect(hasSameSite).to.be.true;
        });
        
        // 验证过期时间
        const expiresPart = cookieParts.find(p => p.toLowerCase().startsWith('expires='));
        if (expiresPart) {
            const expires = new Date(expiresPart.split('=')[1]);
            pm.test('Cookie 过期时间应在未来', function() {
                pm.expect(expires.getTime()).to.be.above(Date.now());
            });
        }
    }
});

6.6 响应时间验证

6.6.1 响应时间断言
javascript 复制代码
// 响应时间验证示例

// 1. 基本响应时间验证
pm.test('响应时间应小于 500ms', function() {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// 2. 响应时间分级验证
pm.test('响应时间应在可接受范围内', function() {
    const responseTime = pm.response.responseTime;
    
    if (responseTime < 100) {
        console.log('响应速度: 极快');
    } else if (responseTime < 300) {
        console.log('响应速度: 快');
    } else if (responseTime < 500) {
        console.log('响应速度: 正常');
    } else if (responseTime < 1000) {
        console.log('响应速度: 较慢');
    } else {
        console.log('响应速度: 过慢');
        pm.expect(responseTime).to.be.below(1000, '响应时间过长');
    }
});

// 3. 根据接口类型设置不同阈值
const endpoint = pm.request.url.path.join('/');
const thresholds = {
    '/api/v1/health': 100,
    '/api/v1/users': 300,
    '/api/v1/reports': 2000,
    '/api/v1/export': 5000
};

const threshold = thresholds[endpoint] || 1000;
pm.test(`响应时间应小于 ${threshold}ms`, function() {
    pm.expect(pm.response.responseTime).to.be.below(threshold);
});

// 4. 响应时间趋势记录
const responseTimeHistory = JSON.parse(pm.environment.get('responseTimeHistory') || '[]');
responseTimeHistory.push({
    endpoint: endpoint,
    time: pm.response.responseTime,
    timestamp: Date.now()
});

// 只保留最近 100 条记录
if (responseTimeHistory.length > 100) {
    responseTimeHistory.shift();
}

pm.environment.set('responseTimeHistory', JSON.stringify(responseTimeHistory));

// 计算平均响应时间
const avgTime = responseTimeHistory.reduce((sum, r) => sum + r.time, 0) / responseTimeHistory.length;
console.log(`平均响应时间: ${avgTime.toFixed(2)}ms`);

6.7 复杂断言场景

6.7.1 条件断言
javascript 复制代码
// 条件断言示例

const response = pm.response.json();

// 根据响应内容决定断言
if (response.data && response.data.length > 0) {
    pm.test('数据列表不应为空', function() {
        pm.expect(response.data.length).to.be.above(0);
    });
    
    // 验证每个数据项
    response.data.forEach((item, index) => {
        pm.test(`数据项 ${index + 1} 应包含 ID`, function() {
            pm.expect(item).to.have.property('id');
        });
    });
} else {
    pm.test('数据列表为空时应有提示', function() {
        pm.expect(response.message).to.include('无数据');
    });
}

// 根据环境变量决定断言严格程度
const strictMode = pm.environment.get('strictMode') === 'true';

if (strictMode) {
    pm.test('严格模式: 所有字段必须存在', function() {
        const requiredFields = ['id', 'name', 'email', 'phone', 'address'];
        requiredFields.forEach(field => {
            pm.expect(response.data).to.have.property(field);
        });
    });
} else {
    pm.test('普通模式: 必要字段必须存在', function() {
        pm.expect(response.data).to.have.property('id');
        pm.expect(response.data).to.have.property('name');
    });
}
6.7.2 数据一致性断言
javascript 复制代码
// 数据一致性断言示例

const response = pm.response.json();

// 1. 关联数据一致性
pm.test('用户ID与订单用户ID应一致', function() {
    const userId = pm.environment.get('userId');
    const orderUserId = response.data.userId;
    pm.expect(orderUserId).to.equal(userId);
});

// 2. 数值计算一致性
pm.test('订单总金额应等于各商品金额之和', function() {
    const order = response.data;
    const calculatedTotal = order.items.reduce((sum, item) => {
        return sum + (item.price * item.quantity);
    }, 0);
    
    pm.expect(order.totalAmount).to.equal(calculatedTotal);
});

// 3. 时间逻辑一致性
pm.test('结束时间应大于开始时间', function() {
    const startTime = new Date(response.data.startTime);
    const endTime = new Date(response.data.endTime);
    pm.expect(endTime.getTime()).to.be.above(startTime.getTime());
});

// 4. 状态流转一致性
pm.test('订单状态流转应合法', function() {
    const validTransitions = {
        'pending': ['paid', 'cancelled'],
        'paid': ['shipped', 'refunded'],
        'shipped': ['delivered', 'returned'],
        'delivered': ['returned'],
        'cancelled': [],
        'refunded': [],
        'returned': []
    };
    
    const previousStatus = pm.environment.get('previousOrderStatus');
    const currentStatus = response.data.status;
    
    if (previousStatus) {
        const allowedNext = validTransitions[previousStatus] || [];
        pm.expect(allowedNext).to.include(currentStatus, 
            `状态从 ${previousStatus} 不能转换到 ${currentStatus}`);
    }
    
    pm.environment.set('previousOrderStatus', currentStatus);
});

// 5. 数据完整性一致性
pm.test('创建时间和更新时间应合理', function() {
    const createdAt = new Date(response.data.createdAt);
    const updatedAt = new Date(response.data.updatedAt);
    
    // 更新时间应大于等于创建时间
    pm.expect(updatedAt.getTime()).to.be.at.least(createdAt.getTime());
    
    // 创建时间应早于当前时间
    pm.expect(createdAt.getTime()).to.be.below(Date.now());
});
6.7.3 批量断言
javascript 复制代码
// 批量断言示例

const response = pm.response.json();

// 1. 批量验证字段存在
const requiredFields = ['id', 'name', 'email', 'status', 'createdAt'];

requiredFields.forEach(field => {
    pm.test(`应包含字段: ${field}`, function() {
        pm.expect(response.data).to.have.property(field);
    });
});

// 2. 批量验证数据类型
const fieldTypes = {
    id: 'number',
    name: 'string',
    email: 'string',
    age: 'number',
    active: 'boolean',
    tags: 'array',
    profile: 'object'
};

Object.entries(fieldTypes).forEach(([field, type]) => {
    pm.test(`字段 ${field} 类型应为 ${type}`, function() {
        if (response.data[field] !== undefined) {
            pm.expect(response.data[field]).to.be.a(type);
        }
    });
});

// 3. 批量验证数组元素
pm.test('所有用户邮箱格式应正确', function() {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    
    response.data.forEach((user, index) => {
        pm.expect(user.email).to.match(emailRegex, 
            `用户 ${index + 1} 的邮箱格式不正确: ${user.email}`);
    });
});

// 4. 批量验证数值范围
pm.test('所有数值应在合理范围内', function() {
    const ranges = {
        age: { min: 0, max: 150 },
        score: { min: 0, max: 100 },
        salary: { min: 0, max: 1000000 }
    };
    
    Object.entries(ranges).forEach(([field, range]) => {
        if (response.data[field] !== undefined) {
            pm.expect(response.data[field]).to.be.within(range.min, range.max,
                `${field} 超出合理范围`);
        }
    });
});

6.8 自定义断言函数

6.8.1 通用断言函数
javascript 复制代码
// 自定义断言函数库

// 1. 验证标准响应格式
function assertStandardResponse(response) {
    pm.test('响应应包含标准格式', function() {
        pm.expect(response).to.have.property('code');
        pm.expect(response).to.have.property('message');
        pm.expect(response).to.have.property('data');
        pm.expect(response.code).to.be.a('number');
        pm.expect(response.message).to.be.a('string');
    });
}

// 2. 验证用户对象
function assertUserObject(user, requiredFields = ['id', 'name', 'email']) {
    pm.test('用户对象应包含必要字段', function() {
        requiredFields.forEach(field => {
            pm.expect(user).to.have.property(field);
        });
    });
    
    pm.test('邮箱格式应正确', function() {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        pm.expect(user.email).to.match(emailRegex);
    });
}

// 3. 验证分页数据
function assertPaginationData(response) {
    pm.test('分页数据应有效', function() {
        pm.expect(response.data).to.be.an('array');
        pm.expect(response.pagination).to.be.an('object');
        pm.expect(response.pagination.page).to.be.at.least(1);
        pm.expect(response.pagination.size).to.be.above(0);
        pm.expect(response.pagination.total).to.be.at.least(0);
    });
}

// 4. 验证日期格式
function assertDateFormat(dateString, format = 'ISO8601') {
    pm.test(`日期格式应为 ${format}`, function() {
        const formats = {
            ISO8601: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/,
            Date: /^\d{4}-\d{2}-\d{2}$/,
            DateTime: /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/
        };
        
        pm.expect(dateString).to.match(formats[format]);
        
        // 验证是否为有效日期
        const date = new Date(dateString);
        pm.expect(date.toString()).to.not.equal('Invalid Date');
    });
}

// 5. 验证数值范围
function assertInRange(value, min, max, fieldName = 'value') {
    pm.test(`${fieldName} 应在 ${min} 到 ${max} 范围内`, function() {
        pm.expect(value).to.be.within(min, max);
    });
}

// 使用示例
const response = pm.response.json();

assertStandardResponse(response);
assertUserObject(response.data);
assertDateFormat(response.data.createdAt);
assertInRange(response.data.age, 0, 150, '年龄');
6.8.2 业务断言函数
javascript 复制代码
// 业务断言函数示例

// 1. 订单断言
function assertOrder(order) {
    pm.test('订单应包含必要信息', function() {
        pm.expect(order).to.have.property('id');
        pm.expect(order).to.have.property('userId');
        pm.expect(order).to.have.property('items');
        pm.expect(order).to.have.property('totalAmount');
        pm.expect(order).to.have.property('status');
    });
    
    pm.test('订单金额应正确计算', function() {
        const calculatedTotal = order.items.reduce((sum, item) => {
            return sum + (item.price * item.quantity);
        }, 0);
        pm.expect(order.totalAmount).to.equal(calculatedTotal);
    });
    
    pm.test('订单状态应有效', function() {
        const validStatuses = ['pending', 'paid', 'shipped', 'delivered', 'cancelled'];
        pm.expect(validStatuses).to.include(order.status);
    });
}

// 2. 支付断言
function assertPayment(payment) {
    pm.test('支付信息应完整', function() {
        pm.expect(payment).to.have.property('id');
        pm.expect(payment).to.have.property('orderId');
        pm.expect(payment).to.have.property('amount');
        pm.expect(payment).to.have.property('method');
        pm.expect(payment).to.have.property('status');
    });
    
    pm.test('支付金额应为正数', function() {
        pm.expect(payment.amount).to.be.above(0);
    });
    
    pm.test('支付方式应有效', function() {
        const validMethods = ['alipay', 'wechat', 'credit_card', 'bank_transfer'];
        pm.expect(validMethods).to.include(payment.method);
    });
}

// 3. 商品断言
function assertProduct(product) {
    pm.test('商品信息应完整', function() {
        pm.expect(product).to.have.property('id');
        pm.expect(product).to.have.property('name');
        pm.expect(product).to.have.property('price');
        pm.expect(product).to.have.property('stock');
    });
    
    pm.test('商品价格应为正数', function() {
        pm.expect(product.price).to.be.above(0);
    });
    
    pm.test('商品库存应为非负数', function() {
        pm.expect(product.stock).to.be.at.least(0);
    });
}

// 使用示例
const response = pm.response.json();

if (response.data.order) {
    assertOrder(response.data.order);
}

if (response.data.payment) {
    assertPayment(response.data.payment);
}

if (response.data.product) {
    assertProduct(response.data.product);
}

6.9 断言最佳实践

6.9.1 断言组织原则
javascript 复制代码
// 断言组织最佳实践

const response = pm.response.json();

// 1. 按功能模块分组
console.group('状态码验证');
pm.test('状态码应为 200', function() {
    pm.response.to.have.status(200);
});
console.groupEnd();

console.group('响应结构验证');
pm.test('响应应包含标准格式', function() {
    pm.expect(response).to.have.property('code');
    pm.expect(response).to.have.property('data');
});
console.groupEnd();

console.group('业务数据验证');
pm.test('用户数据应有效', function() {
    pm.expect(response.data.id).to.be.a('number');
    pm.expect(response.data.name).to.be.a('string');
});
console.groupEnd();

// 2. 使用描述性测试名称
// 好的命名
pm.test('用户邮箱应符合标准格式 (xxx@xxx.xxx)', function() {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    pm.expect(response.data.email).to.match(emailRegex);
});

// 不好的命名
// pm.test('邮箱测试', function() { ... });

// 3. 一个测试一个断言点
// 推荐
pm.test('用户ID应为正整数', function() {
    pm.expect(response.data.id).to.be.above(0);
    pm.expect(Number.isInteger(response.data.id)).to.be.true;
});

// 不推荐(多个不相关的断言)
// pm.test('用户数据验证', function() {
//     pm.expect(response.data.id).to.be.above(0);
//     pm.expect(response.data.name).to.be.a('string');
//     pm.expect(response.data.email).to.match(/@/);
// });
6.9.2 断言错误处理
javascript 复制代码
// 断言错误处理

const response = pm.response.json();

// 1. 安全断言
pm.test('安全验证用户数据', function() {
    // 先检查数据是否存在
    if (!response.data) {
        pm.expect.fail('响应数据为空');
        return;
    }
    
    // 再进行具体验证
    pm.expect(response.data).to.have.property('id');
});

// 2. 带错误信息的断言
pm.test('带错误提示的断言', function() {
    pm.expect(response.data.age, '年龄应为正整数').to.be.above(0);
    pm.expect(response.data.email, '邮箱格式不正确').to.match(/@/);
});

// 3. 断言失败时的详细信息
pm.test('详细错误信息', function() {
    try {
        pm.expect(response.data.items).to.have.length.above(0);
    } catch (error) {
        console.error('断言失败详情:', {
            expected: 'items 长度大于 0',
            actual: `items 长度为 ${response.data.items?.length || 0}`,
            data: response.data
        });
        throw error;
    }
});

// 4. 条件断言失败处理
pm.test('条件断言', function() {
    const condition = response.data && response.data.status === 'active';
    
    if (!condition) {
        console.warn('跳过活跃用户专属验证');
        return;  // 不执行后续断言
    }
    
    // 活跃用户专属验证
    pm.expect(response.data.lastLoginAt).to.exist;
});
相关推荐
Jianghong Jian2 小时前
Hashcat:强大的密码恢复与安全测试工具
测试工具·安全·密码学
椰椰椰耶2 小时前
接口性能测试:Postman与Fiddler双剑合璧
测试工具·fiddler·postman
我的xiaodoujiao5 小时前
3、API 接口自动化测试详细图文教程学习系列3--相关Python基础知识2
python·学习·测试工具·pytest
紫丁香6 小时前
Postman 自动化测试完全指南1
接口测试·postman
小陈的进阶之路7 小时前
接口测试分析及用例设计
测试工具
123过去8 小时前
impacket-mssqlclient使用教程
linux·测试工具·安全
Saniffer_SH8 小时前
【高清视频】实验室搭建PCIe 6.0测试环境需要的retimer卡介绍
服务器·驱动开发·测试工具·fpga开发·计算机外设·硬件架构·压力测试