当前位置: 首页 > news >正文

前端测试自动化实战:基于Jest与Cypress构建完整测试流水线

1. 项目概述:为什么前端测试自动化是必选项?

如果你还在手动刷新页面、点点点来验证功能,那可能已经落后了不止一个版本了。前端测试自动化,早已不是“锦上添花”的加分项,而是保障现代Web应用交付质量、提升团队协作效率的“基础设施”。想象一下,一个拥有几十上百个交互组件的单页应用,每次发版前,测试同学都要花上几天时间进行回归测试,不仅人力成本高,重复劳动让人疲惫,更可怕的是,人工测试难免会有疏漏,一个不起眼的边界条件就可能引发线上事故。这就是我们为什么要拥抱自动化测试——让机器去执行那些重复、枯燥但至关重要的验证工作,把人解放出来去做更有创造性的探索性测试和业务分析。

这个实战指南的核心,就是围绕JestCypress这两个当前最主流、最强大的前端测试工具,构建一套从单元测试到端到端(E2E)测试的完整自动化流水线。Jest 以其“零配置”和强大的快照测试、Mock功能,成为单元和集成测试的绝对王者;而 Cypress 则以其独特的运行机制、实时重载和时光旅行调试,彻底改变了E2E测试的开发者体验。将它们组合起来,你就能覆盖从单个函数、组件的行为,到整个应用在真实浏览器中运行状态的全方位质量防线。这不仅仅是写几个测试用例那么简单,而是建立一套可持续运行、快速反馈、并能融入CI/CD流程的工程实践。无论你是正在从零搭建测试体系的前端团队负责人,还是希望提升个人工程化能力的开发者,这套“组合拳”都能为你提供一条清晰、可落地的路径。

2. 测试策略与工具选型:Jest + Cypress 为何是黄金组合?

在开始敲代码之前,我们必须先理清测试金字塔的概念,并理解为什么是Jest和Cypress,而不是其他工具。测试金字塔由下至上分别是:单元测试(最多)、集成测试(中等)、端到端测试(最少)。底层测试运行快、成本低、定位问题准,应作为主体;顶层测试模拟真实用户场景,但运行慢、维护成本高,应作为关键路径的保障。

2.1 Jest:单元与集成测试的基石

Jest 是 Facebook 出品的一个专注于“简单性”的JavaScript测试框架。它的优势非常明显:

  • 开箱即用:几乎不需要配置,安装即跑,内置了测试运行器、断言库、Mock系统和覆盖率报告。
  • 快照测试:这是Jest的杀手锏之一。它能捕获UI组件、配置文件甚至任何可序列化数据的“快照”,并与后续版本进行比对,非常适合检测UI的意外变更。
  • 强大的Mocking:前端测试中,模拟HTTP请求、模块依赖、定时器是家常便饭。Jest提供了从函数、模块到定时器的一整套Mock方案,让你能轻松隔离测试环境。
  • 并行与缓存:Jest默认并行运行测试,并利用缓存只运行改动的测试,速度极快。

注意:虽然Jest常被用于React生态,但它完全框架无关。在Vue、Angular甚至Node.js后端项目中,Jest同样表现出色。不要被它的“出身”局限了。

2.2 Cypress:端到端测试的革命者

传统的E2E测试工具如Selenium,是在浏览器外部通过WebDriver协议进行遥控,测试脚本和浏览器运行在不同的进程中。而Cypress采用了完全不同的架构:

  • 同源架构:Cypress测试代码与应用程序运行在同一个浏览器循环(loop)中。这意味着它能直接访问DOM、Window对象,并能同步执行命令,彻底避免了“等待”和“竞态条件”这类Selenium中的经典难题。
  • 时光旅行:Cypress在运行测试时会自动截图和录制视频。更重要的是,其内置的“时光旅行”调试工具,允许你在测试执行后,回退到任意命令执行时的状态,直观地查看当时的DOM、网络请求和Console日志。
  • 实时重载:当你修改测试代码或应用代码时,Cypress会自动重新运行测试,提供无与伦比的开发体验。
  • 网络流量控制:无需启动后端服务,Cypress就能轻松Stub(存根)和Spy(监听)网络请求,让你能测试各种边界场景(如网络错误、慢速响应)。

2.3 为什么是它们俩?

  • 职责清晰,覆盖全面:Jest负责底层逻辑(工具函数、组件方法、状态管理)的正确性;Cypress负责顶层用户旅程(登录、下单、支付)的流畅性。两者结合,无死角覆盖。
  • 开发者体验至上:两者都以提升开发者体验为核心目标。Jest的快反馈和Cypress的实时调试,让编写测试从“负担”变成“乐趣”。
  • 生态与社区:它们都拥有极其活跃的社区和丰富的插件生态,遇到问题很容易找到解决方案或最佳实践。
  • 与现代前端工具链无缝集成:无论是Webpack、Vite、Babel还是TypeScript,它们都有成熟的配置方案,能轻松融入你的项目。

3. 环境搭建与项目初始化

理论说再多,不如动手搭一个。我们假设你有一个基于Vite构建的React项目(其他框架原理相通)。让我们从零开始,搭建这个测试环境。

3.1 初始化项目与安装Jest

首先,如果你还没有项目,可以用以下命令快速创建一个:

npm create vite@latest my-frontend-app -- --template react-ts cd my-frontend-app npm install

接下来,安装Jest及其相关依赖。虽然Vite官方推荐Vitest,但Jest的生态和稳定性目前依然更胜一筹。我们需要安装核心包和适用于React的预设。

npm install --save-dev jest @types/jest ts-jest jest-environment-jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
  • jest: Jest核心库。
  • @types/jest: TypeScript类型定义。
  • ts-jest: 让Jest能够处理TypeScript文件。
  • jest-environment-jsdom: 提供一个类浏览器的DOM环境,用于测试涉及DOM操作的组件。
  • @testing-library/react&@testing-library/jest-dom&@testing-library/user-event: React Testing Library (RTL) 三件套。这是当前React组件测试的事实标准,它鼓励你像用户一样测试组件,而非测试其内部实现细节。

3.2 配置Jest

在项目根目录创建jest.config.js文件:

/** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { // 使用 ts-jest 预设来处理 TypeScript preset: 'ts-jest', // 测试环境设置为 jsdom,以模拟浏览器环境 testEnvironment: 'jest-environment-jsdom', // 告诉 Jest 如何处理不同类型的文件 transform: { '^.+\\.tsx?$': 'ts-jest', }, // 匹配测试文件,通常放在 __tests__ 目录下或以 .test/.spec 结尾 testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'], // 设置模块别名(如果你的项目配置了,比如 @/ -> src/) moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1', }, // 每次测试前自动执行的脚本,常用于设置全局的测试工具 setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], };

然后创建jest.setup.js文件,用于引入一些全局的测试扩展:

// 引入 jest-dom 的扩展断言,如 toBeInTheDocument, toHaveClass 等 import '@testing-library/jest-dom'; // 可以在这里配置全局的测试前/后钩子

3.3 安装与配置Cypress

Cypress的安装同样简单。我们安装其核心包和用于组件测试的包(可选,但推荐)。

npm install --save-dev cypress @cypress/react @cypress/webpack-dev-server

安装完成后,初始化Cypress。这会创建默认的文件夹结构和配置文件。

npx cypress open

第一次运行会弹出Cypress的图形化界面,并让你选择测试类型(E2E或组件测试)。选择E2E Testing,它会自动创建cypress.config.tscypress/fixturescypress/supportcypress/e2e目录。

我们需要调整cypress.config.ts来适配我们的Vite项目:

import { defineConfig } from 'cypress'; import webpackPreprocessor from '@cypress/webpack-dev-server'; export default defineConfig({ e2e: { // 设置测试文件的基础路径 specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', // 支持组件测试(如果需要) // supportComponentTesting: true, // 配置开发服务器 setupNodeEvents(on, config) { // 如果使用webpack,可以在这里配置,但Vite项目更推荐使用 vite-plugin-cypress 或直接使用Vite dev server // 我们这里采用更简单的方式:直接代理到本地开发服务器 }, // 基础URL,Cypress将在此URL下运行测试 baseUrl: 'http://localhost:5173', // Vite默认开发服务器端口 }, // 组件测试配置(如果启用) component: { devServer: { framework: 'react', bundler: 'vite', }, }, });

一个更实用的技巧是,在package.json中添加脚本,同时启动开发服务器和Cypress:

{ "scripts": { "dev": "vite", "build": "vite build", "test:unit": "jest", "test:e2e": "cypress run", "test:e2e:open": "cypress open", "test": "npm run test:unit && npm run test:e2e" } }

4. Jest单元与集成测试实战

环境搭好了,我们来写点真正的测试。我们从最简单的工具函数测试开始,再到复杂的React组件测试。

4.1 工具函数测试

假设我们有一个工具函数src/utils/math.ts

// 一个简单的加法函数,但有一些边界处理 export function add(a: number, b: number): number { if (typeof a !== 'number' || typeof b !== 'number') { throw new TypeError('Parameters must be numbers'); } // 模拟一个浮点数精度问题 return parseFloat((a + b).toFixed(2)); }

为其创建测试文件src/utils/math.test.ts

import { add } from './math'; describe('add function', () => { // 测试正常功能 it('should add two positive numbers correctly', () => { expect(add(1, 2)).toBe(3); expect(add(0.1, 0.2)).toBe(0.3); // 注意浮点数精度,我们的函数已处理 }); // 测试负数 it('should handle negative numbers', () => { expect(add(-1, 5)).toBe(4); expect(add(-2, -3)).toBe(-5); }); // 测试边界/异常情况 it('should throw TypeError for non-number inputs', () => { // 注意:测试异步错误或抛出错误的函数,需要将断言包装在一个函数中 expect(() => add('1' as any, 2)).toThrow(TypeError); expect(() => add(1, null as any)).toThrow('Parameters must be numbers'); }); // 测试浮点数精度处理 it('should fix floating point precision', () => { // 0.1 + 0.2 在JS中等于 0.30000000000000004 expect(add(0.1, 0.2)).toBe(0.3); expect(add(1.005, 2.005)).toBe(3.01); // 1.005+2.005=3.01, toFixed(2)后正确 }); });

运行npm run test:unit,Jest会找到这个测试文件并执行。describe用于分组,it(或test)用于定义一个具体的测试用例。expect是断言,toBe是匹配器(Matcher)。Jest提供了丰富的匹配器,如toEqual(深度比较对象)、toHaveBeenCalledWith(检查函数调用参数)等。

4.2 React组件测试(使用React Testing Library)

这是前端测试的重头戏。假设我们有一个简单的计数器组件src/components/Counter.tsx

import { useState } from 'react'; interface CounterProps { initialCount?: number; } export function Counter({ initialCount = 0 }: CounterProps) { const [count, setCount] = useState(initialCount); const increment = () => setCount(count + 1); const decrement = () => setCount(count - 1); const reset = () => setCount(initialCount); return ( <div> <h2>import { render, screen, fireEvent } from '@testing-library/react'; import { Counter } from './Counter'; import '@testing-library/jest-dom'; // 引入扩展断言 describe('Counter Component', () => { // 测试初始渲染 it('renders with initial count', () => { render(<Counter initialCount={5} />); // 通过文本内容查找元素 const displayElement = screen.getByText(/count: 5/i); expect(displayElement).toBeInTheDocument(); // 使用 jest-dom 的扩展断言 }); // 测试交互:点击增加按钮 it('increments count when + button is clicked', () => { render(<Counter />); const incrementButton = screen.getByRole('button', { name: /increment/i }); const displayElement = screen.getByTestId('count-display'); // 使用>// src/services/api.ts export async function fetchUserData(userId: string) { const response = await fetch(`/api/users/${userId}`); return response.json(); } // src/components/UserProfile.tsx import { useEffect, useState } from 'react'; import { fetchUserData } from '../services/api';

在测试中,我们不应该真的发起网络请求。我们需要Mock这个fetchUserData函数。

// src/components/UserProfile.test.tsx import { render, screen, waitFor } from '@testing-library/react'; import UserProfile from './UserProfile'; import { fetchUserData } from '../services/api'; // 1. 使用 jest.mock 自动模拟整个模块 jest.mock('../services/api'); // 2. 将模拟后的模块转换为 jest.Mocked 类型以获得类型安全 const mockedFetchUserData = fetchUserData as jest.MockedFunction<typeof fetchUserData>; describe('UserProfile', () => { it('displays user data after successful fetch', async () => { // 3. 为模拟函数设置返回值 const mockUser = { id: '1', name: 'John Doe', email: 'john@example.com' }; mockedFetchUserData.mockResolvedValueOnce(mockUser); // 模拟一次成功的异步调用 render(<UserProfile userId="1" />); // 初始应为加载状态 expect(screen.getByText(/loading/i)).toBeInTheDocument(); // 使用 waitFor 等待异步操作完成和UI更新 await waitFor(() => { expect(screen.getByText(mockUser.name)).toBeInTheDocument(); expect(screen.getByText(mockUser.email)).toBeInTheDocument(); }); // 验证函数被以正确的参数调用 expect(mockedFetchUserData).toHaveBeenCalledWith('1'); expect(mockedFetchUserData).toHaveBeenCalledTimes(1); }); it('displays error message when fetch fails', async () => { // 模拟一次失败的调用 mockedFetchUserData.mockRejectedValueOnce(new Error('Network Error')); render(<UserProfile userId="2" />); await waitFor(() => { expect(screen.getByText(/failed to load user/i)).toBeInTheDocument(); }); }); });

实操心得:Mock是单元测试的灵魂,但不要过度Mock。如果一个测试文件里充满了jest.mock,你可能需要反思组件的设计是否耦合过紧。理想情况下,应通过Props传递依赖,或者使用像@testing-library/react-hooks这样的工具来测试自定义Hook。

4.4 快照测试

快照测试用于捕获组件渲染输出的结构,防止意外更改。它非常适合用于不经常变化的展示型组件或配置对象。

// 在 Counter.test.tsx 中添加 it('matches snapshot', () => { const { container } = render(<Counter initialCount={42} />); expect(container.firstChild).toMatchSnapshot(); });

第一次运行测试时,Jest会在__snapshots__目录下生成一个.snap文件,里面是组件渲染的字符串表示。后续运行时,Jest会将新的渲染结果与快照对比。如果不同,测试会失败。这时你需要检查差异:如果是预期的改动,按u键更新快照;如果是bug,则修复组件。

注意事项:快照测试不能替代具体的断言。它容易产生“虚假安全”(因为任何改动都会导致失败,你可能不假思索地更新快照)。应将其作为辅助手段,与具体的交互测试结合使用。

5. Cypress端到端测试实战

单元测试保证了“零件”的质量,E2E测试则要验证组装好的“汽车”能跑。Cypress让这个过程变得直观。

5.1 编写第一个E2E测试

假设我们有一个简单的待办事项应用。我们编写一个测试用户故事:“用户访问首页,添加一个新的待办事项,并验证它出现在列表中”。 在cypress/e2e/todo.cy.ts中:

describe('Todo Application', () => { // 每个测试用例运行前执行,通常用于访问被测页面 beforeEach(() => { // 访问本地开发服务器。baseUrl 在 cypress.config.ts 中配置 cy.visit('/'); }); it('should allow user to add a new todo item', () => { // 1. 断言页面加载成功,包含关键元素 cy.get('h1').should('contain.text', 'Todo List'); cy.get('input[placeholder="Add a new todo..."]').should('be.visible'); // 2. 用户输入文本并提交 const newTodoText = 'Learn Cypress E2E Testing'; cy.get('input[placeholder="Add a new todo..."]').type(newTodoText); cy.get('button').contains('Add').click(); // 3. 断言新事项出现在列表中,且输入框被清空 cy.get('.todo-list li') .should('have.length', 1) // 假设初始列表为空 .last() // 获取最后一项(即刚添加的) .should('contain.text', newTodoText); cy.get('input[placeholder="Add a new todo..."]').should('have.value', ''); // 输入框应清空 }); it('should mark a todo item as completed', () => { // 先添加一个事项 cy.get('input').type('Item to complete{enter}'); // {enter} 模拟回车键提交 // 找到这个事项的复选框并勾选 cy.get('.todo-list li') .first() .within(() => { // within 将查询范围限定在当前元素内 cy.get('input[type="checkbox"]').check(); }); // 断言事项被标记为完成(可能有样式变化) cy.get('.todo-list li') .first() .should('have.class', 'completed') // 假设完成的事项有 .completed 类 .find('label') // 找到文本标签 .should('have.css', 'text-decoration', 'line-through solid rgb(0, 0, 0)'); // 更具体的样式断言 }); it('should delete a todo item', () => { // 添加两个事项 cy.get('input').type('Item A{enter}'); cy.get('input').type('Item B{enter}'); // 删除第一个事项 cy.get('.todo-list li') .first() .within(() => { cy.get('button.delete').click(); // 假设每个事项有个删除按钮 }); // 断言只剩下一个事项,且是“Item B” cy.get('.todo-list li') .should('have.length', 1) .and('contain.text', 'Item B'); }); });

Cypress的命令是链式调用的,并且具有自动重试机制。例如,cy.get(...).should('be.visible')会持续尝试查找元素直到它可见(默认超时4秒),这极大地增强了测试的稳定性,无需手动添加sleep

5.2 网络请求的拦截与存根(Stubbing)

E2E测试不应该依赖不稳定的后端服务。Cypress可以轻松拦截和存根网络请求。假设我们的待办事项是从API加载的。

describe('Todo App with API', () => { it('loads initial todos from API', () => { // 在访问页面之前,拦截特定的API请求 cy.intercept('GET', '/api/todos', { statusCode: 200, body: [ { id: 1, text: 'Mocked Todo 1', completed: false }, { id: 2, text: 'Mocked Todo 2', completed: true }, ], }).as('getTodos'); // 给这个拦截请求起个别名 cy.visit('/'); // 等待这个拦截请求完成(可选,用于确保请求已发生) cy.wait('@getTodos'); // 断言页面显示了模拟的数据 cy.get('.todo-list li').should('have.length', 2); cy.contains('Mocked Todo 1').should('be.visible'); cy.contains('Mocked Todo 2').should('be.visible'); }); it('shows error message when API fails', () => { cy.intercept('GET', '/api/todos', { statusCode: 500, body: { error: 'Internal Server Error' }, delay: 1000, // 模拟网络延迟 }).as('failedRequest'); cy.visit('/'); cy.wait('@failedRequest'); // 断言错误提示出现 cy.get('.error-message').should('contain.text', 'Failed to load todos'); }); });

cy.intercept()是Cypress最强大的功能之一。你可以用它来:

  1. 存根(Stub):直接返回模拟数据,不发送真实请求。
  2. 监听(Spy):让请求正常发出,但监听其请求和响应,用于断言。
  3. 修改响应:拦截真实请求并修改其响应体或状态码。

5.3 使用自定义命令和Fixtures

为了提高代码复用性,可以将常用操作封装为自定义命令。例如,登录操作在很多测试中都需要。 在cypress/support/commands.ts中添加:

// 声明自定义命令的类型(在 cypress/support/index.d.ts 或全局声明文件中更好) declare global { namespace Cypress { interface Chainable { /** * 自定义命令:使用给定凭据登录 * @example cy.login('test@example.com', 'password123') */ login(email?: string, password?: string): Chainable<Element>; } } } Cypress.Commands.add('login', (email = 'test@example.com', password = 'password123') => { cy.intercept('POST', '/api/login').as('loginRequest'); cy.visit('/login'); cy.get('input[name="email"]').type(email); cy.get('input[name="password"]').type(password); cy.get('button[type="submit"]').click(); cy.wait('@loginRequest'); // 可以在这里断言登录成功,比如跳转到首页 cy.url().should('include', '/dashboard'); });

然后在测试中就可以直接使用cy.login()

Fixtures用于存放静态测试数据,如JSON、图片等。在cypress/fixtures目录下创建example-todos.json

[ { "id": 101, "text": "Fixture Todo One", "completed": false }, { "id": 102, "text": "Fixture Todo Two", "completed": true } ]

在测试中使用:

cy.fixture('example-todos').then((todos) => { cy.intercept('GET', '/api/todos', todos); cy.visit('/'); // ... 断言 });

6. 集成与持续集成(CI)流程

测试写好了,如何让它自动运行,成为质量守门员?

6.1 本地脚本集成

我们已经配置了package.json脚本。可以运行npm test来依次执行单元测试和E2E测试。但E2E测试需要应用在运行。我们可以使用concurrentlystart-server-and-test这类工具来编排。

npm install --save-dev start-server-and-test

修改package.json

{ "scripts": { "dev": "vite", "build": "vite build", "test:unit": "jest", "test:e2e": "cypress run", "test:e2e:open": "cypress open", "test:e2e:ci": "start-server-and-test 'npm run dev' http://localhost:5173 'npm run test:e2e'", "test:ci": "npm run test:unit && npm run test:e2e:ci" } }

start-server-and-test会先执行第一个命令(启动服务器),然后轮询第二个参数给出的URL直到可访问,最后执行第三个命令(运行测试)。测试结束后,它会自动关闭服务器。

6.2 集成到GitHub Actions

在项目根目录创建.github/workflows/test.yml

name: CI Tests on: [push, pull_request] # 在推送代码或创建PR时触发 jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' # 使用你的项目Node版本 cache: 'npm' - name: Install dependencies run: npm ci # 使用 ci 而不是 install,确保依赖锁一致 - name: Run unit tests with Jest run: npm run test:unit # 可以添加覆盖率报告上传步骤 # env: # CI: true - name: Run E2E tests with Cypress uses: cypress-io/github-action@v6 with: build: npm run build # 先构建生产版本 start: npm run preview # 使用Vite预览服务器,或直接用 `npm run dev` 但需要配置 wait-on wait-on: 'http://localhost:4173' # Vite预览默认端口 # 或者使用 start-server-and-test # start: npm run test:e2e:ci # Cypress Action 会自动处理服务器的启动、等待和测试执行

这个工作流会在每次代码变更时自动运行你的全套测试。如果测试失败,PR将无法合并,从而强制保证主分支代码的质量。

6.3 测试报告与可视化

  • Jest:运行jest --coverage会生成一个代码覆盖率报告(HTML格式),存放在coverage目录。你可以配置CI将其上传到如CodecovCoveralls等服务。
  • Cypress:运行cypress run默认会在cypress/videoscypress/screenshots中生成测试录像和失败截图。在CI中,你可以将这些作为构件(Artifacts)上传,方便失败时查看。Cypress Dashboard 服务(付费)提供了更强大的测试记录、并行化、负载均衡功能。

7. 常见问题、调试技巧与最佳实践

7.1 Jest常见问题

  • 测试无法识别ESM模块:如果你的项目或依赖使用ES Modules,Jest可能需要额外配置。可以使用jest.config.js中的transformIgnorePatterns排除某些不需要转换的node_modules,或者使用@jest/experimental开启对ESM的实验性支持。
  • “act(...)”警告:在测试涉及状态更新的组件时(如使用useEffect),RTL可能会输出此警告。使用await waitFor(...)findBy*查询器(如screen.findByText)来等待异步更新。对于更复杂的情况,可以使用act@testing-library/react导入并手动包装。
  • Mock不生效:确保jest.mock语句在文件顶部,在任何导入之前。Jest的模块模拟机制会在导入前生效。

7.2 Cypress常见问题

  • “元素未找到”或“超时”:这是最常见的问题。首先,使用Cypress的选择器检查器(打开Cypress Test Runner,点击选择器工具)来确认你的选择器是否能唯一找到元素。其次,确保你的操作在正确的时机(例如,等待数据加载完成后再点击按钮)。善用.should()进行断言式等待,而不是硬性等待cy.wait(5000)
  • 跨域问题:Cypress默认禁止访问不同顶级域名的页面。如果你的测试涉及导航到另一个域名(如SSO登录),需要配置chromeWebSecurity: falsecypress.config.ts中,但这会降低一些安全性。更好的做法是使用cy.origin()来隔离跨域上下文。
  • 测试不稳定(Flaky Tests):不稳定的测试是CI/CD的毒药。主要原因有:1)网络/API依赖:使用cy.intercept()彻底存根不稳定的后端调用。2)动画/过渡效果:使用{ force: true }选项或cy.config('defaultCommandTimeout', 10000)增加超时。3)第三方小部件:如地图、聊天插件,考虑在测试环境中禁用它们,或使用cy.clock()来控制时间。

7.3 最佳实践清单

  • 测试原则
    • 测试行为,而非实现:不要测试组件内部状态或方法名。测试用户能看到和交互的东西(文本、按钮、输入框)。
    • 保持测试独立:每个测试不应该依赖其他测试的状态或外部环境。使用beforeEach进行清理和初始化。
    • 优先单元,谨慎E2E:测试金字塔。用大量快速、低成本的单元测试覆盖核心逻辑,用少量关键的E2E测试覆盖核心用户流程。
  • Jest/RTL实践
    • 为交互元素添加>
http://www.gsyq.cn/news/1591311.html

相关文章:

  • 随机重入流水车间调度优化:从并行机模型到智能策略的工程实践
  • Windows热键冲突终极侦探:3步找出占用热键的“小偷“程序
  • 有哪些AI论文软件是真的适配学科专业,而不是空洞拼凑?
  • 2026会务系统推荐对比:为什么会助力成了多数主办方的最终选择
  • 模板变参与折叠表达式精讲,可变参数模板原理、参数包展开、折叠表达式、万能参数解析、日志/序列化高阶实战
  • AS9653与LMX2820调试
  • 第5课:机器学习的基本类型
  • OpenAI发布自研推理芯片Jalapeño,9个月流片,英伟达大客户纷纷“造反”!
  • 1. 字符缓冲流复制文本文件
  • 6月24日RoboScience发布通用具身大模型,具身智能破局泛化难题有新招!
  • 2026全栈信创选型深度指南:AI Agent兼容国产芯片的架构博弈与提效实战
  • Prime Day来袭!ZDNET编辑精选90多款优惠,7款iPhone小工具超值折扣
  • 2026 AI/LLM黑话速通:Prefill、RLVR、GraphRAG,进阶概念怎么用?从小白到听懂面试官在说什么(下)
  • 做工控品质7年掏心窝分享:选串口屏别乱踩坑
  • 推荐题目:洛谷 P1049 [NOIP 2001 普及组] 装箱问题
  • 免费虚拟桌面伴侣:5个功能让你打造独一无二的二次元伙伴
  • WAVES 2026大会聚焦具身智能:创业者与投资人共探落地路径与商业前景
  • Andromeda:爱奇艺开源的 Android 组件通信框架
  • 工程化工具链
  • 开目PLM:基于协同工作区和骨架模型驱动的三维协同设计
  • 第3课:机器如何“学习”
  • 社会网络分析入门:从佛罗伦萨家族数据看网络中心性与结构洞
  • 接口测试实战:从Postman基础到分层用例设计方法论
  • CentOS安装KVM两种方案:系统自带组件与yum一键安装
  • 连续折腾两周 AI 项目后,我发现真正影响开发效率的,从来不只是模型能力 —— 一次使用蓝耘 MaaS 的真实记录
  • 基于51单片机的智能香薰灯:从PID温控到WS2812B灯效的嵌入式开发实践
  • A2A 协议落地 —— 从“前瞻设计“到“标准化接入“
  • 人类全部知识·全域数学统一学习总纲-(Ω-终版·2026.06.28·全覆盖UNESCO 5260门人类学科)
  • crypto-js AES ECB模式跨语言加解密避坑指南
  • STM32-S256-儿童锁+水温度检测+出水量+液位+防干烧+保温沸腾常温+自动+手动+加热+出水+OLED屏+声光提醒+(无线方式选择)-34(设计源文件+万字报告+讲解)(支持资料、图片参考_相