Files
niumalll/docs/测试指南.md

29 KiB
Raw Blame History

活牛采购智能数字化系统 - 测试指南

版本历史

版本 日期 作者 变更说明
v1.0 2024-12-20 测试团队 初版测试指南

1. 测试概述

1.1 测试策略

本系统采用多层次测试策略,确保系统质量和稳定性:

  • 单元测试: 测试单个函数和模块
  • 集成测试: 测试模块间的交互
  • API测试: 测试接口功能和性能
  • 端到端测试: 测试完整业务流程
  • 性能测试: 测试系统性能和负载能力
  • 安全测试: 测试系统安全性

1.2 测试环境

测试环境配置

# 测试环境要求
Node.js >= 18.0.0
MySQL >= 8.0 (测试数据库)
Redis >= 6.0 (测试缓存)
Docker >= 20.10 (容器测试)

测试数据库配置

-- 创建测试数据库
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配置文件

// 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
};

测试环境配置

// 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 模型测试

用户模型测试

// 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);
    });
  });
});

订单模型测试

// 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 服务层测试

用户服务测试

// 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 运行单元测试

# 安装测试依赖
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配置

// 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测试

// 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测试

// 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配置

// 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 用户注册登录流程测试

// 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 订单创建流程测试

// 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 负载测试配置

// 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 数据库性能测试

-- 数据库性能测试脚本
-- 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注入测试

// 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测试

// 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 认证授权测试

// 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 测试脚本配置

{
  "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集成

# .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 测试报告生成

# 生成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 测试团队

9.2 相关文档


注意: 请确保在生产环境部署前通过所有测试用例,并达到覆盖率要求。