单元测试、集成测试、E2E测试、部署策略、CI/CD
本课程将详细介绍React应用的测试与部署相关知识和实践技巧,包括单元测试、集成测试、E2E测试的基本概念和工具,以及部署策略、CI/CD流程等核心内容。
// 单元测试基础
// 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);
});
});
// 集成测试示例
// 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();
});
});
// 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
// 构建优化
// 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目录中
// 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)
// 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
// 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 }}
// 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 (
发生错误,请刷新页面重试 为一个简单的React组件编写单元测试,包含以下内容:
配置CI/CD流程,包含以下内容:
部署一个React应用到生产环境,包含以下内容:
为一个完整的React应用编写测试套件并部署,包含以下内容:
通过本课程学习,你应该掌握了React应用测试与部署的核心知识和实践技巧:
测试和部署是React应用开发的重要组成部分,良好的测试策略可以提高代码质量和可靠性,而有效的部署流程可以确保应用的平稳发布和运行。继续深入学习和实践,你将能够构建更加专业和可靠的React应用。