Files
niumalll/docs/测试指南.md

1181 lines
29 KiB
Markdown
Raw Normal View History

# 活牛采购智能数字化系统 - 测试指南
## 版本历史
| 版本 | 日期 | 作者 | 变更说明 |
|------|------|------|----------|
| v1.0 | 2024-12-20 | 测试团队 | 初版测试指南 |
## 1. 测试概述
### 1.1 测试策略
本系统采用多层次测试策略,确保系统质量和稳定性:
- **单元测试**: 测试单个函数和模块
- **集成测试**: 测试模块间的交互
- **API测试**: 测试接口功能和性能
- **端到端测试**: 测试完整业务流程
- **性能测试**: 测试系统性能和负载能力
- **安全测试**: 测试系统安全性
### 1.2 测试环境
#### 测试环境配置
```bash
# 测试环境要求
Node.js >= 18.0.0
MySQL >= 8.0 (测试数据库)
Redis >= 6.0 (测试缓存)
Docker >= 20.10 (容器测试)
```
#### 测试数据库配置
```sql
-- 创建测试数据库
CREATE DATABASE niumall_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建测试用户
CREATE USER 'test_user'@'localhost' IDENTIFIED BY 'test_password';
GRANT ALL PRIVILEGES ON niumall_test.* TO 'test_user'@'localhost';
FLUSH PRIVILEGES;
```
## 2. 单元测试
### 2.1 测试框架配置
#### Jest配置文件
```javascript
// jest.config.js
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: [
'**/__tests__/**/*.js',
'**/?(*.)+(spec|test).js'
],
collectCoverageFrom: [
'src/**/*.js',
'!src/**/*.test.js',
'!src/config/**',
'!src/migrations/**'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
testTimeout: 10000,
verbose: true
};
```
#### 测试环境配置
```javascript
// tests/setup.js
const { sequelize } = require('../src/models');
// 测试前设置
beforeAll(async () => {
// 连接测试数据库
await sequelize.authenticate();
// 同步数据库结构
await sequelize.sync({ force: true });
});
// 测试后清理
afterAll(async () => {
// 关闭数据库连接
await sequelize.close();
});
// 每个测试前清理数据
beforeEach(async () => {
// 清理测试数据
await sequelize.truncate({ cascade: true });
});
```
### 2.2 模型测试
#### 用户模型测试
```javascript
// tests/models/user.test.js
const { User } = require('../../src/models');
describe('User Model', () => {
describe('创建用户', () => {
test('应该成功创建有效用户', async () => {
const userData = {
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
const user = await User.create(userData);
expect(user.id).toBeDefined();
expect(user.phone).toBe(userData.phone);
expect(user.name).toBe(userData.name);
expect(user.role).toBe(userData.role);
expect(user.password).not.toBe(userData.password); // 应该被加密
});
test('应该拒绝无效手机号', async () => {
const userData = {
phone: '123',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
await expect(User.create(userData)).rejects.toThrow();
});
test('应该拒绝重复手机号', async () => {
const userData = {
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
await User.create(userData);
await expect(User.create(userData)).rejects.toThrow();
});
});
describe('用户验证', () => {
test('应该正确验证密码', async () => {
const password = 'password123';
const user = await User.create({
phone: '13800138000',
password,
name: '测试用户',
role: 'buyer'
});
const isValid = await user.validatePassword(password);
expect(isValid).toBe(true);
const isInvalid = await user.validatePassword('wrongpassword');
expect(isInvalid).toBe(false);
});
});
});
```
#### 订单模型测试
```javascript
// tests/models/order.test.js
const { Order, User } = require('../../src/models');
describe('Order Model', () => {
let buyer, seller;
beforeEach(async () => {
buyer = await User.create({
phone: '13800138001',
password: 'password123',
name: '采购商',
role: 'buyer'
});
seller = await User.create({
phone: '13800138002',
password: 'password123',
name: '养殖户',
role: 'seller'
});
});
test('应该成功创建订单', async () => {
const orderData = {
buyer_id: buyer.id,
seller_id: seller.id,
cattle_type: '肉牛',
quantity: 50,
unit_price: 15000,
total_amount: 750000,
delivery_date: new Date('2024-12-25'),
delivery_address: '测试地址'
};
const order = await Order.create(orderData);
expect(order.id).toBeDefined();
expect(order.status).toBe('pending');
expect(order.total_amount).toBe(orderData.total_amount);
});
test('应该正确计算订单总金额', async () => {
const order = await Order.create({
buyer_id: buyer.id,
seller_id: seller.id,
cattle_type: '肉牛',
quantity: 50,
unit_price: 15000,
delivery_date: new Date('2024-12-25'),
delivery_address: '测试地址'
});
expect(order.total_amount).toBe(50 * 15000);
});
});
```
### 2.3 服务层测试
#### 用户服务测试
```javascript
// tests/services/userService.test.js
const UserService = require('../../src/services/userService');
const { User } = require('../../src/models');
describe('UserService', () => {
describe('用户注册', () => {
test('应该成功注册新用户', async () => {
const userData = {
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
const result = await UserService.register(userData);
expect(result.success).toBe(true);
expect(result.user.phone).toBe(userData.phone);
expect(result.token).toBeDefined();
});
test('应该拒绝已存在的手机号', async () => {
const userData = {
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
await UserService.register(userData);
const result = await UserService.register(userData);
expect(result.success).toBe(false);
expect(result.message).toContain('手机号已存在');
});
});
describe('用户登录', () => {
beforeEach(async () => {
await UserService.register({
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
});
});
test('应该成功登录有效用户', async () => {
const result = await UserService.login('13800138000', 'password123');
expect(result.success).toBe(true);
expect(result.user.phone).toBe('13800138000');
expect(result.token).toBeDefined();
});
test('应该拒绝错误密码', async () => {
const result = await UserService.login('13800138000', 'wrongpassword');
expect(result.success).toBe(false);
expect(result.message).toContain('密码错误');
});
test('应该拒绝不存在的用户', async () => {
const result = await UserService.login('13800138999', 'password123');
expect(result.success).toBe(false);
expect(result.message).toContain('用户不存在');
});
});
});
```
### 2.4 运行单元测试
```bash
# 安装测试依赖
npm install --save-dev jest supertest
# 运行所有测试
npm test
# 运行特定测试文件
npm test -- tests/models/user.test.js
# 运行测试并生成覆盖率报告
npm run test:coverage
# 监听模式运行测试
npm run test:watch
```
## 3. API测试
### 3.1 API测试框架
#### Supertest配置
```javascript
// tests/api/setup.js
const request = require('supertest');
const app = require('../../src/app');
// 创建测试客户端
const api = request(app);
// 辅助函数获取认证token
async function getAuthToken(userData = {}) {
const defaultUser = {
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
const user = { ...defaultUser, ...userData };
// 注册用户
await api.post('/api/auth/register').send(user);
// 登录获取token
const response = await api
.post('/api/auth/login')
.send({
phone: user.phone,
password: user.password
});
return response.body.token;
}
module.exports = { api, getAuthToken };
```
### 3.2 认证API测试
```javascript
// tests/api/auth.test.js
const { api } = require('./setup');
describe('Auth API', () => {
describe('POST /api/auth/register', () => {
test('应该成功注册新用户', async () => {
const userData = {
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
};
const response = await api
.post('/api/auth/register')
.send(userData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.user.phone).toBe(userData.phone);
expect(response.body.token).toBeDefined();
});
test('应该拒绝无效数据', async () => {
const invalidData = {
phone: '123',
password: '123'
};
const response = await api
.post('/api/auth/register')
.send(invalidData)
.expect(400);
expect(response.body.success).toBe(false);
expect(response.body.errors).toBeDefined();
});
});
describe('POST /api/auth/login', () => {
beforeEach(async () => {
await api.post('/api/auth/register').send({
phone: '13800138000',
password: 'password123',
name: '测试用户',
role: 'buyer'
});
});
test('应该成功登录', async () => {
const response = await api
.post('/api/auth/login')
.send({
phone: '13800138000',
password: 'password123'
})
.expect(200);
expect(response.body.success).toBe(true);
expect(response.body.token).toBeDefined();
});
test('应该拒绝错误密码', async () => {
const response = await api
.post('/api/auth/login')
.send({
phone: '13800138000',
password: 'wrongpassword'
})
.expect(401);
expect(response.body.success).toBe(false);
});
});
});
```
### 3.3 订单API测试
```javascript
// tests/api/orders.test.js
const { api, getAuthToken } = require('./setup');
describe('Orders API', () => {
let buyerToken, sellerToken;
beforeEach(async () => {
buyerToken = await getAuthToken({
phone: '13800138001',
role: 'buyer'
});
sellerToken = await getAuthToken({
phone: '13800138002',
role: 'seller'
});
});
describe('POST /api/orders', () => {
test('采购商应该能创建订单', async () => {
const orderData = {
seller_phone: '13800138002',
cattle_type: '肉牛',
quantity: 50,
unit_price: 15000,
delivery_date: '2024-12-25',
delivery_address: '测试地址'
};
const response = await api
.post('/api/orders')
.set('Authorization', `Bearer ${buyerToken}`)
.send(orderData)
.expect(201);
expect(response.body.success).toBe(true);
expect(response.body.order.status).toBe('pending');
});
test('应该拒绝未认证的请求', async () => {
const orderData = {
seller_phone: '13800138002',
cattle_type: '肉牛',
quantity: 50,
unit_price: 15000
};
await api
.post('/api/orders')
.send(orderData)
.expect(401);
});
});
describe('GET /api/orders', () => {
test('应该返回用户的订单列表', async () => {
// 先创建一个订单
await api
.post('/api/orders')
.set('Authorization', `Bearer ${buyerToken}`)
.send({
seller_phone: '13800138002',
cattle_type: '肉牛',
quantity: 50,
unit_price: 15000,
delivery_date: '2024-12-25',
delivery_address: '测试地址'
});
const response = await api
.get('/api/orders')
.set('Authorization', `Bearer ${buyerToken}`)
.expect(200);
expect(response.body.success).toBe(true);
expect(Array.isArray(response.body.orders)).toBe(true);
expect(response.body.orders.length).toBeGreaterThan(0);
});
});
});
```
## 4. 端到端测试
### 4.1 Cypress配置
```javascript
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: 'cypress/support/e2e.js',
specPattern: 'cypress/e2e/**/*.cy.js',
video: true,
screenshot: true,
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 10000,
requestTimeout: 10000,
responseTimeout: 10000
}
});
```
### 4.2 用户注册登录流程测试
```javascript
// cypress/e2e/auth.cy.js
describe('用户认证流程', () => {
beforeEach(() => {
// 清理数据库
cy.task('db:seed');
});
it('应该完成完整的注册登录流程', () => {
// 访问注册页面
cy.visit('/register');
// 填写注册表单
cy.get('[data-cy=phone-input]').type('13800138000');
cy.get('[data-cy=password-input]').type('password123');
cy.get('[data-cy=name-input]').type('测试用户');
cy.get('[data-cy=role-select]').select('buyer');
// 提交注册
cy.get('[data-cy=register-button]').click();
// 验证注册成功
cy.url().should('include', '/dashboard');
cy.get('[data-cy=user-name]').should('contain', '测试用户');
// 退出登录
cy.get('[data-cy=logout-button]').click();
// 重新登录
cy.visit('/login');
cy.get('[data-cy=phone-input]').type('13800138000');
cy.get('[data-cy=password-input]').type('password123');
cy.get('[data-cy=login-button]').click();
// 验证登录成功
cy.url().should('include', '/dashboard');
});
});
```
### 4.3 订单创建流程测试
```javascript
// cypress/e2e/orders.cy.js
describe('订单管理流程', () => {
beforeEach(() => {
cy.task('db:seed');
// 登录为采购商
cy.login('buyer');
});
it('应该完成完整的订单创建流程', () => {
// 访问订单创建页面
cy.visit('/orders/create');
// 填写订单信息
cy.get('[data-cy=seller-select]').select('养殖户A');
cy.get('[data-cy=cattle-type-input]').type('肉牛');
cy.get('[data-cy=quantity-input]').type('50');
cy.get('[data-cy=unit-price-input]').type('15000');
cy.get('[data-cy=delivery-date-input]').type('2024-12-25');
cy.get('[data-cy=delivery-address-input]').type('测试地址');
// 提交订单
cy.get('[data-cy=create-order-button]').click();
// 验证订单创建成功
cy.get('[data-cy=success-message]').should('be.visible');
cy.url().should('include', '/orders');
// 验证订单出现在列表中
cy.get('[data-cy=order-list]').should('contain', '肉牛');
cy.get('[data-cy=order-status]').should('contain', '待确认');
});
it('应该能查看订单详情', () => {
// 创建测试订单
cy.createTestOrder();
// 访问订单列表
cy.visit('/orders');
// 点击查看详情
cy.get('[data-cy=order-item]').first().click();
// 验证详情页面
cy.url().should('include', '/orders/');
cy.get('[data-cy=order-details]').should('be.visible');
cy.get('[data-cy=cattle-type]').should('contain', '肉牛');
cy.get('[data-cy=quantity]').should('contain', '50');
});
});
```
## 5. 性能测试
### 5.1 负载测试配置
```javascript
// tests/performance/load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
// 自定义指标
export let errorRate = new Rate('errors');
// 测试配置
export let options = {
stages: [
{ duration: '2m', target: 100 }, // 2分钟内增加到100用户
{ duration: '5m', target: 100 }, // 保持100用户5分钟
{ duration: '2m', target: 200 }, // 2分钟内增加到200用户
{ duration: '5m', target: 200 }, // 保持200用户5分钟
{ duration: '2m', target: 0 }, // 2分钟内减少到0用户
],
thresholds: {
http_req_duration: ['p(99)<1500'], // 99%的请求在1.5秒内完成
http_req_failed: ['rate<0.1'], // 错误率小于10%
errors: ['rate<0.1'], // 自定义错误率小于10%
},
};
// 测试数据
const users = [
{ phone: '13800138001', password: 'password123' },
{ phone: '13800138002', password: 'password123' },
{ phone: '13800138003', password: 'password123' },
];
export default function () {
// 随机选择用户
const user = users[Math.floor(Math.random() * users.length)];
// 登录
let loginResponse = http.post('http://localhost:3000/api/auth/login', {
phone: user.phone,
password: user.password,
});
let loginSuccess = check(loginResponse, {
'登录状态码为200': (r) => r.status === 200,
'登录响应时间<500ms': (r) => r.timings.duration < 500,
});
errorRate.add(!loginSuccess);
if (loginSuccess) {
const token = JSON.parse(loginResponse.body).token;
// 获取订单列表
let ordersResponse = http.get('http://localhost:3000/api/orders', {
headers: { Authorization: `Bearer ${token}` },
});
let ordersSuccess = check(ordersResponse, {
'订单列表状态码为200': (r) => r.status === 200,
'订单列表响应时间<1000ms': (r) => r.timings.duration < 1000,
});
errorRate.add(!ordersSuccess);
// 创建订单
let createOrderResponse = http.post(
'http://localhost:3000/api/orders',
JSON.stringify({
seller_phone: '13800138002',
cattle_type: '肉牛',
quantity: Math.floor(Math.random() * 100) + 1,
unit_price: 15000,
delivery_date: '2024-12-25',
delivery_address: '测试地址',
}),
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
let createSuccess = check(createOrderResponse, {
'创建订单状态码为201': (r) => r.status === 201,
'创建订单响应时间<2000ms': (r) => r.timings.duration < 2000,
});
errorRate.add(!createSuccess);
}
sleep(1);
}
```
### 5.2 数据库性能测试
```sql
-- 数据库性能测试脚本
-- tests/performance/db-performance.sql
-- 创建测试数据
DELIMITER //
CREATE PROCEDURE GenerateTestData()
BEGIN
DECLARE i INT DEFAULT 1;
-- 创建10000个用户
WHILE i <= 10000 DO
INSERT INTO users (phone, password, name, role, created_at, updated_at)
VALUES (
CONCAT('138', LPAD(i, 8, '0')),
'$2b$10$hash',
CONCAT('用户', i),
CASE WHEN i % 3 = 0 THEN 'seller' ELSE 'buyer' END,
NOW(),
NOW()
);
SET i = i + 1;
END WHILE;
-- 创建50000个订单
SET i = 1;
WHILE i <= 50000 DO
INSERT INTO orders (
buyer_id, seller_id, cattle_type, quantity, unit_price,
total_amount, status, delivery_date, delivery_address,
created_at, updated_at
)
VALUES (
FLOOR(RAND() * 6667) + 1, -- 随机买家
FLOOR(RAND() * 3333) + 6668, -- 随机卖家
CASE FLOOR(RAND() * 3)
WHEN 0 THEN '肉牛'
WHEN 1 THEN '奶牛'
ELSE '种牛'
END,
FLOOR(RAND() * 100) + 1,
FLOOR(RAND() * 5000) + 10000,
0, -- 将通过触发器计算
CASE FLOOR(RAND() * 5)
WHEN 0 THEN 'pending'
WHEN 1 THEN 'confirmed'
WHEN 2 THEN 'in_transit'
WHEN 3 THEN 'delivered'
ELSE 'completed'
END,
DATE_ADD(NOW(), INTERVAL FLOOR(RAND() * 30) DAY),
CONCAT('地址', i),
NOW() - INTERVAL FLOOR(RAND() * 365) DAY,
NOW()
);
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
-- 执行数据生成
CALL GenerateTestData();
-- 性能测试查询
-- 1. 测试订单列表查询性能
EXPLAIN ANALYZE
SELECT o.*, u1.name as buyer_name, u2.name as seller_name
FROM orders o
JOIN users u1 ON o.buyer_id = u1.id
JOIN users u2 ON o.seller_id = u2.id
WHERE o.status = 'pending'
ORDER BY o.created_at DESC
LIMIT 20;
-- 2. 测试用户订单统计性能
EXPLAIN ANALYZE
SELECT
u.id,
u.name,
COUNT(o.id) as order_count,
SUM(o.total_amount) as total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.buyer_id
WHERE u.role = 'buyer'
GROUP BY u.id, u.name
ORDER BY total_amount DESC
LIMIT 100;
-- 3. 测试复杂查询性能
EXPLAIN ANALYZE
SELECT
DATE(o.created_at) as order_date,
o.cattle_type,
COUNT(*) as order_count,
AVG(o.unit_price) as avg_price,
SUM(o.total_amount) as total_amount
FROM orders o
WHERE o.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND o.status IN ('completed', 'delivered')
GROUP BY DATE(o.created_at), o.cattle_type
ORDER BY order_date DESC, total_amount DESC;
```
## 6. 安全测试
### 6.1 SQL注入测试
```javascript
// tests/security/sql-injection.test.js
const { api, getAuthToken } = require('../api/setup');
describe('SQL注入安全测试', () => {
let token;
beforeEach(async () => {
token = await getAuthToken();
});
test('应该防止登录接口SQL注入', async () => {
const maliciousInputs = [
"' OR '1'='1",
"'; DROP TABLE users; --",
"' UNION SELECT * FROM users --",
"admin'--",
"' OR 1=1#"
];
for (const input of maliciousInputs) {
const response = await api
.post('/api/auth/login')
.send({
phone: input,
password: input
});
// 应该返回401而不是500或其他错误
expect([400, 401]).toContain(response.status);
expect(response.body.success).toBe(false);
}
});
test('应该防止订单查询SQL注入', async () => {
const maliciousQueries = [
"1' OR '1'='1",
"1; DROP TABLE orders; --",
"1 UNION SELECT * FROM users"
];
for (const query of maliciousQueries) {
const response = await api
.get(`/api/orders/${query}`)
.set('Authorization', `Bearer ${token}`);
// 应该返回400或404不应该执行恶意SQL
expect([400, 404]).toContain(response.status);
}
});
});
```
### 6.2 XSS测试
```javascript
// tests/security/xss.test.js
const { api, getAuthToken } = require('../api/setup');
describe('XSS安全测试', () => {
let token;
beforeEach(async () => {
token = await getAuthToken();
});
test('应该过滤用户输入中的脚本', async () => {
const xssPayloads = [
'<script>alert("xss")</script>',
'<img src="x" onerror="alert(1)">',
'javascript:alert("xss")',
'<svg onload="alert(1)">',
'"><script>alert("xss")</script>'
];
for (const payload of xssPayloads) {
const response = await api
.post('/api/orders')
.set('Authorization', `Bearer ${token}`)
.send({
seller_phone: '13800138002',
cattle_type: payload,
quantity: 50,
unit_price: 15000,
delivery_date: '2024-12-25',
delivery_address: payload
});
if (response.status === 201) {
// 如果创建成功,检查返回的数据是否被正确转义
expect(response.body.order.cattle_type).not.toContain('<script>');
expect(response.body.order.delivery_address).not.toContain('<script>');
}
}
});
});
```
### 6.3 认证授权测试
```javascript
// tests/security/auth.test.js
const { api, getAuthToken } = require('../api/setup');
describe('认证授权安全测试', () => {
test('应该拒绝无效token', async () => {
const invalidTokens = [
'invalid-token',
'Bearer invalid',
'',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.invalid',
];
for (const token of invalidTokens) {
const response = await api
.get('/api/orders')
.set('Authorization', token);
expect(response.status).toBe(401);
}
});
test('应该防止权限提升', async () => {
// 创建普通用户token
const userToken = await getAuthToken({ role: 'buyer' });
// 尝试访问管理员接口
const response = await api
.get('/api/admin/users')
.set('Authorization', `Bearer ${userToken}`);
expect(response.status).toBe(403);
});
test('应该防止越权访问', async () => {
// 创建两个用户
const user1Token = await getAuthToken({
phone: '13800138001',
role: 'buyer'
});
const user2Token = await getAuthToken({
phone: '13800138002',
role: 'buyer'
});
// 用户1创建订单
const orderResponse = await api
.post('/api/orders')
.set('Authorization', `Bearer ${user1Token}`)
.send({
seller_phone: '13800138003',
cattle_type: '肉牛',
quantity: 50,
unit_price: 15000,
delivery_date: '2024-12-25',
delivery_address: '测试地址'
});
const orderId = orderResponse.body.order.id;
// 用户2尝试访问用户1的订单
const accessResponse = await api
.get(`/api/orders/${orderId}`)
.set('Authorization', `Bearer ${user2Token}`);
expect(accessResponse.status).toBe(403);
});
});
```
## 7. 测试执行
### 7.1 测试脚本配置
```json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:unit": "jest tests/unit",
"test:integration": "jest tests/integration",
"test:api": "jest tests/api",
"test:security": "jest tests/security",
"test:e2e": "cypress run",
"test:e2e:open": "cypress open",
"test:performance": "k6 run tests/performance/load-test.js",
"test:all": "npm run test:unit && npm run test:api && npm run test:security && npm run test:e2e"
}
}
```
### 7.2 CI/CD集成
```yaml
# .github/workflows/test.yml
name: 测试流水线
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: niumall_test
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
redis:
image: redis:7-alpine
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- uses: actions/checkout@v3
- name: 设置Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 运行单元测试
run: npm run test:unit
env:
NODE_ENV: test
DB_HOST: localhost
DB_PORT: 3306
DB_NAME: niumall_test
DB_USER: root
DB_PASSWORD: root
REDIS_HOST: localhost
REDIS_PORT: 6379
- name: 运行API测试
run: npm run test:api
env:
NODE_ENV: test
- name: 运行安全测试
run: npm run test:security
env:
NODE_ENV: test
- name: 生成测试报告
run: npm run test:coverage
env:
NODE_ENV: test
- name: 上传覆盖率报告
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
- name: 运行端到端测试
run: npm run test:e2e
env:
NODE_ENV: test
```
## 8. 测试报告
### 8.1 测试覆盖率要求
- **代码覆盖率**: ≥ 80%
- **分支覆盖率**: ≥ 75%
- **函数覆盖率**: ≥ 85%
- **行覆盖率**: ≥ 80%
### 8.2 性能基准
- **API响应时间**: P99 < 1.5秒
- **数据库查询**: P95 < 500ms
- **并发用户**: 支持200并发用户
- **错误率**: < 1%
### 8.3 测试报告生成
```bash
# 生成HTML测试报告
npm run test:coverage
# 生成性能测试报告
k6 run --out json=performance-report.json tests/performance/load-test.js
# 生成安全测试报告
npm run test:security -- --reporters=json --outputFile=security-report.json
```
## 9. 联系信息
### 9.1 测试团队
- **测试负责人**: test-lead@niumall.com
- **自动化测试**: automation@niumall.com
- **性能测试**: performance@niumall.com
### 9.2 相关文档
- [项目总览](./项目总览.md)
- [产品需求文档](./产品需求文档.md)
- [系统架构设计](./系统架构设计.md)
- [部署运维指南](./部署运维指南.md)
---
**注意**: 请确保在生产环境部署前通过所有测试用例,并达到覆盖率要求。