<返回目录     Powered by claud/xia兄

第14课: 测试与部署

单元测试、集成测试、E2E测试、部署策略、CI/CD

课程内容

本课程将详细介绍React应用的测试与部署相关知识和实践技巧,包括单元测试、集成测试、E2E测试的基本概念和工具,以及部署策略、CI/CD流程等核心内容。

核心概念

1. 测试基础

2. 单元测试

2.1 Jest和React Testing Library

// 单元测试基础
// 1. 安装测试依赖
// npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event

// 2. 配置Jest
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "setupFilesAfterEnv": ["/src/setupTests.js"]
  }
}

// src/setupTests.js
import '@testing-library/jest-dom';

// 3. 编写单元测试
// components/Button.test.jsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';

describe('Button组件', () => {
  test('渲染按钮并显示正确的文本', () => {
    render();
    const buttonElement = screen.getByText('点击我');
    expect(buttonElement).toBeInTheDocument();
  });

  test('点击按钮触发onClick事件', () => {
    const handleClick = jest.fn();
    render();
    const buttonElement = screen.getByText('点击我');
    fireEvent.click(buttonElement);
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  test('禁用状态下点击按钮不触发onClick事件', () => {
    const handleClick = jest.fn();
    render();
    const buttonElement = screen.getByText('点击我');
    fireEvent.click(buttonElement);
    expect(handleClick).not.toHaveBeenCalled();
  });

  test('应用正确的className', () => {
    render();
    const buttonElement = screen.getByText('点击我');
    expect(buttonElement).toHaveClass('custom-button');
  });
});

// 4. 测试自定义Hook
// hooks/useCounter.test.js
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';

describe('useCounter Hook', () => {
  test('初始化计数器为0', () => {
    const { result } = renderHook(() => useCounter());
    expect(result.current.count).toBe(0);
  });

  test('初始化计数器为指定值', () => {
    const { result } = renderHook(() => useCounter(5));
    expect(result.current.count).toBe(5);
  });

  test('调用increment增加计数', () => {
    const { result } = renderHook(() => useCounter());
    act(() => {
      result.current.increment();
    });
    expect(result.current.count).toBe(1);
  });

  test('调用decrement减少计数', () => {
    const { result } = renderHook(() => useCounter(5));
    act(() => {
      result.current.decrement();
    });
    expect(result.current.count).toBe(4);
  });

  test('调用reset重置计数', () => {
    const { result } = renderHook(() => useCounter(5));
    act(() => {
      result.current.increment();
      result.current.reset();
    });
    expect(result.current.count).toBe(5);
  });
});

2.2 测试最佳实践

3. 集成测试

3.1 组件集成测试

// 集成测试示例
// components/UserProfile.test.jsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import UserProfile from './UserProfile';

// Mock API调用
jest.mock('../services/api', () => ({
  fetchUser: jest.fn(() => Promise.resolve({
    id: 1,
    name: '张三',
    email: 'zhangsan@example.com',
    avatar: 'https://example.com/avatar.jpg'
  }))
}));

describe('UserProfile组件', () => {
  test('加载用户数据并显示用户信息', async () => {
    render();
    
    // 初始显示加载状态
    expect(screen.getByText('加载中...')).toBeInTheDocument();
    
    // 等待数据加载完成
    const userName = await screen.findByText('张三');
    expect(userName).toBeInTheDocument();
    expect(screen.getByText('zhangsan@example.com')).toBeInTheDocument();
    expect(screen.getByAltText('用户头像')).toBeInTheDocument();
  });

  test('显示错误信息当加载失败时', async () => {
    // Mock失败的API调用
    const { fetchUser } = require('../services/api');
    fetchUser.mockRejectedValue(new Error('加载失败'));
    
    render();
    
    // 等待错误信息显示
    const errorMessage = await screen.findByText('加载失败');
    expect(errorMessage).toBeInTheDocument();
  });
});

4. E2E测试

4.1 Cypress

// E2E测试基础
// 1. 安装Cypress
// npm install --save-dev cypress

// 2. 配置Cypress
// package.json
{
  "scripts": {
    "e2e": "cypress open",
    "e2e:run": "cypress run"
  }
}

// 3. 编写E2E测试
// cypress/e2e/homepage.cy.js
describe('首页测试', () => {
  beforeEach(() => {
    // 访问首页
    cy.visit('http://localhost:3000');
  });

  it('显示首页标题', () => {
    cy.contains('欢迎来到React应用').should('be.visible');
  });

  it('导航到关于页', () => {
    // 点击导航链接
    cy.contains('关于我们').click();
    // 验证URL变更
    cy.url().should('include', '/about');
    // 验证页面内容
    cy.contains('关于我们').should('be.visible');
  });

  it('提交联系表单', () => {
    // 导航到联系页
    cy.contains('联系我们').click();
    // 填写表单
    cy.get('input[name="name"]').type('测试用户');
    cy.get('input[name="email"]').type('test@example.com');
    cy.get('textarea[name="message"]').type('这是一条测试消息');
    // 提交表单
    cy.get('button[type="submit"]').click();
    // 验证成功消息
    cy.contains('消息发送成功').should('be.visible');
  });
});

// 4. 运行E2E测试
// 启动开发服务器
// npm run dev
// 运行Cypress
// npm run e2e

5. 部署基础

5.1 构建优化

// 构建优化
// 1. 配置Vite
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  build: {
    // 生成sourcemap
    sourcemap: false,
    // 分割代码
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
          redux: ['@reduxjs/toolkit', 'react-redux']
        }
      }
    }
  }
});

// 2. 环境变量配置
// .env.production
VITE_API_URL=https://api.example.com
VITE_APP_ENV=production

// 3. 构建命令
// package.json
{
  "scripts": {
    "build": "vite build"
  }
}

// 4. 运行构建
// npm run build
// 构建产物将生成在dist目录中

5.2 部署目标

6. 部署策略

6.1 静态网站部署

// Netlify部署
// 1. 连接GitHub仓库
// 2. 配置构建命令
// Build command: npm run build
// Publish directory: dist

// 3. 配置环境变量
// 在Netlify dashboard中设置环境变量

// Vercel部署
// 1. 连接GitHub仓库
// 2. 配置项目
// Framework: React
// Build command: npm run build
// Output directory: dist

// GitHub Pages部署
// 1. 安装gh-pages
// npm install --save-dev gh-pages

// 2. 配置package.json
{
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d dist"
  },
  "homepage": "https://username.github.io/repo-name"
}

// 3. 部署
// npm run deploy

// AWS S3部署
// 1. 创建S3存储桶
// 2. 配置存储桶为静态网站托管
// 3. 上传构建产物
// aws s3 sync dist/ s3://your-bucket-name

// 4. 配置CloudFront(可选,用于CDN)

6.2 容器化部署

// Docker部署
// 1. 创建Dockerfile
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

// 2. 创建nginx.conf
server {
  listen 80;
  server_name localhost;
  
  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html;
  }
  
  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/html;
  }
}

// 3. 构建Docker镜像
// docker build -t react-app .

// 4. 运行Docker容器
// docker run -p 8080:80 react-app

// 5. 推送Docker镜像到仓库
// docker tag react-app username/react-app
// docker push username/react-app

7. CI/CD流程

7.1 GitHub Actions

// GitHub Actions配置
// .github/workflows/ci-cd.yml
name: CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: 设置Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 18
        cache: 'npm'
    - name: 安装依赖
      run: npm install
    - name: 运行测试
      run: npm test
    - name: 代码检查
      run: npm run lint

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    - name: 设置Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 18
        cache: 'npm'
    - name: 安装依赖
      run: npm install
    - name: 构建项目
      run: npm run build
      env:
        VITE_API_URL: ${{ secrets.VITE_API_URL }}
    - name: 部署到Netlify
      uses: nwtgck/actions-netlify@v1.2
      with:
        publish-dir: ./dist
        production-branch: main
        github-token: ${{ secrets.GITHUB_TOKEN }}
        deploy-message: "Deploy from GitHub Actions"
        enable-pull-request-comment: false
        enable-commit-comment: true
      env:
        NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
        NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

7.2 CI/CD最佳实践

8. 监控与日志

8.1 应用监控

8.2 日志管理

// Sentry配置
// 1. 安装Sentry
// npm install --save @sentry/react @sentry/tracing

// 2. 初始化Sentry
// src/main.jsx
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import App from './App';

// 初始化Sentry
Sentry.init({
  dsn: 'your-sentry-dsn',
  integrations: [new BrowserTracing()],
  tracesSampleRate: 1.0,
});

const root = createRoot(document.getElementById('root'));
root.render(
  
    
      
    
  
);

// 3. 捕获错误
// 手动捕获错误
try {
  // 可能出错的代码
} catch (error) {
  Sentry.captureException(error);
}

// 捕获路由错误
// App.jsx
import { ErrorBoundary } from '@sentry/react';

function App() {
  return (
    发生错误,请刷新页面重试
}> {/* 应用内容 */} ); }
最佳实践:
注意事项:

实践练习

练习任务:
  1. 基础练习:

    为一个简单的React组件编写单元测试,包含以下内容:

    • 组件渲染测试
    • 用户交互测试
    • 边界情况测试
  2. 中级练习:

    配置CI/CD流程,包含以下内容:

    • 自动化测试
    • 代码检查
    • 自动部署
  3. 高级练习:

    部署一个React应用到生产环境,包含以下内容:

    • 构建优化
    • 环境变量配置
    • 选择合适的部署平台
    • 配置监控系统
  4. 综合练习:

    为一个完整的React应用编写测试套件并部署,包含以下内容:

    • 单元测试(组件和Hook)
    • 集成测试(组件交互)
    • E2E测试(用户流程)
    • CI/CD配置
    • 生产环境部署

总结

通过本课程学习,你应该掌握了React应用测试与部署的核心知识和实践技巧:

测试和部署是React应用开发的重要组成部分,良好的测试策略可以提高代码质量和可靠性,而有效的部署流程可以确保应用的平稳发布和运行。继续深入学习和实践,你将能够构建更加专业和可靠的React应用。