类型概述
静态 Static:在编写代码时捕捉拼写错误和类型错误。
单元 Unit:验证单独的、隔离的部门按预期工作。
单元测试是测试编写方式中最底子的一种,它专注于验证代码的最小可测试单元。通常,这个单元是一个函数、方法或类。单元测试的目的是确保每个独立的代码单元可以大概精确执行其预期功能。
在编写单元测试时,开发职员需要模拟外部依靠,以便在隔离情况中测试目标代码。这种方法有助于快速定位和修复题目,同时也提高了代码的可维护性和可重用性。单元测试通常由开发职员在编写功能代码的同时创建,是连续集成和连续交付(CI/CD)流程中的紧张组成部门。
单元测试的优势在于其执行速度快、成本低,且可以大概提供即时反馈。然而,它也有局限性,主要体现在无法测试组件之间的交互和体系整体行为。这就需要引入其他测试编写方式来增补这一不敷。
集成 Integration:验证几个单元之间的协调工作。
集成测试是测试编写方式中的紧张一环,它关注于验证多个组件或模块之间的交互是否正常。这种测试方法旨在发现单元测试无法检测到的题目,如接口不匹配、数据通报错误等。
在进行集成测试时,开发团队通常会接纳自顶向下或自底向上的方法。自顶向下的方法从体系的主要模块开始,渐渐整合下层模块;而自底向上的方法则从最基本的组件开始,渐渐构建更复杂的功能。
集成测试的优点是可以大概在体系各部门组适时及早发现题目,有助于保证体系的整体稳固性。然而,相比单元测试,集成测试的编写和维护成本较高,执行时间也相对较长。因此,在实际项目中需要平衡单元测试和集成测试的比例,以获得最佳的测试效果。
端到端 End to End:一个像用户一样行为的辅助呆板人,点击应用步调并验证其精确运行。偶然称为“功能测试”或e2e。
端到端测试是测试编写方式中最全面的一种,它模拟真实用户的操作流程,从用户界面到后端数据库,全面验证整个体系的功能和性能。这种测试方法可以大概发现在真实情况中大概出现的题目,如用户界面交互非常、体系响应时间过长等。
编写端到端测试时,测试职员需要筹划完整的用户场景,包括正常流程和非常情况。这通常涉及使用主动化测试工具来模拟用户操作,如点击、输入、页面导航等。端到端测试的效果可以大概直观地反映体系的整体质量和用户体验。
端到端测试的主要优势在于它可以大概验证体系在真实情况中的体现,提供最接近用户实际使用情况的测试效果。然而,这种测试方式也面临一些挑衅,如测试情况的复杂性、执行时间长、维护成本高等。因此,在实际项目中,端到端测试通常作为其他测试方法的增补,重点关注关键业务流程和高风险功能。
常见用例
端到端 End to End:cypress
- import {generate} from 'todo-test-utils'
- // 描述模块:todo 应用程序
- describe('todo app', () => {
- // 测试用例:应该对典型用户起作用
- it('should work for a typical user', () => {
- // 生成一个用户
- const user = generate.user()
- // 生成一个待办事项
- const todo = generate.todo()
- // 这里我们正在经历注册过程。通常我只会有一个端到端测试来执行此操作。
- // 其余的测试将命中应用程序所使用的相同端点,因此我们可以跳过该体验的导航。
- cy.visitApp() // 访问应用程序
- cy.findByText(/register/i).click() // 找到包含"register"文本的元素并点击
- cy.findByLabelText(/username/i).type(user.username) // 找到标签文本为"username"的元素并输入用户的用户名
- cy.findByLabelText(/password/i).type(user.password) // 找到标签文本为"password"的元素并输入用户的密码
- cy.findByText(/login/i).click() // 找到包含"login"文本的元素并点击
- cy.findByLabelText(/add todo/i) // 找到标签文本为"add todo"的元素
- .type(todo.description) // 输入待办事项的描述
- .type('{enter}') // 模拟按下回车键
- cy.findByTestId('todo-0').should('have.value', todo.description) // 断言具有特定测试 ID 的元素的值为待办事项的描述
- cy.findByLabelText('complete').click() // 找到标签文本为"complete"的元素并点击
- cy.findByTestId('todo-0').should('have.class', 'complete') // 断言具有特定测试 ID 的元素具有"complete"类
- // 等等...
- // 我的端到端测试通常表现得类似于用户的行为。它们有时可能相当长。
- })
- })
复制代码 集成 Integration:可用cypress或jest或当前的testing-library
- import * as React from'react';
- import {render, screen, waitForElementToBeRemoved} from 'test/app-test-utils'; // 从测试相关的工具模块中导入渲染函数、获取屏幕元素的方法和等待元素移除的方法
- import userEvent from '@testing-library/user-event'; // 导入模拟用户操作的模块
- import {build, fake} from '@jackfranklin/test-data-bot'; // 导入用于生成测试数据的模块
- import {rest} from'msw'; // 从 MSW 中导入请求相关的方法
- import {setupServer} from'msw/node'; // 从 MSW 的 Node 版本中导入设置服务器的方法
- import {handlers} from 'test/server-handlers'; // 导入服务器处理程序模块
- import App from '../app'; // 导入应用程序组件
- // 构建登录表单数据
- const buildLoginForm = build({
- fields: {
- username: fake(f => f.internet.userName()),
- password: fake(f => f.internet.password()),
- },
- });
- // 通常集成测试仅通过 MSW 模拟 HTTP 请求
- const server = setupServer(...handlers);
- // 在所有测试用例之前执行,启动服务器监听
- beforeAll(() => server.listen());
- // 在所有测试用例之后执行,关闭服务器
- afterAll(() => server.close());
- // 在每个测试用例之后执行,重置服务器处理程序
- afterEach(() => server.resetHandlers());
- test(`登录后显示用户的用户名`, async () => {
- // 自定义渲染返回一个在应用程序完成加载时解决的承诺(如果是服务器渲染,可能不需要这个)
- // 自定义渲染还允许指定初始路由
- await render(<App />, {route: '/login'});
- const {username, password} = buildLoginForm();
- userEvent.type(screen.getByLabelText(/username/i), username);
- userEvent.type(screen.getByLabelText(/password/i), password);
- userEvent.click(screen.getByRole('button', {name: /submit/i}));
- // 等待加载元素被移除
- await waitForElementToBeRemoved(() => screen.getByLabelText(/loading/i));
- // 断言需要验证用户已登录的任何内容
- expect(screen.getByText(username)).toBeInTheDocument();
- });
复制代码 单元 Unit:jest
- // 对组件进行测试 accordion.test.tsx
- // 导入测试相关的模块和函数
- import { afterEach, beforeEach, describe, expect, jest, test } from '@jest/globals';
- import Enzyme, { mount } from 'enzyme'; // 导入酶库用于组件测试
- import Adapter from 'enzyme-adapter-react-16'; // 特定版本的酶适配器
- import toJSON from 'enzyme-to-json'; // 将酶组件转换为 JSON 格式的工具
- import JestMock from 'jest-mock'; // 模拟相关的工具
- import React from'react';
- import { Accordion } from '..'; // 导入要测试的组件
- Enzyme.configure({ adapter: new Adapter() }); // 配置酶使用指定的适配器,根据项目的 React 版本来适配
- describe('Accordion', () => { // 描述一个名为"Accordion"的测试套件
- // 测试套件中用于存储模拟函数和组件包装器的变量
- let onChange: JestMock.Mock<any, any>;
- let wrapper: Enzyme.ReactWrapper;
- beforeEach(() => { // 在每个测试用例运行前执行的操作
- onChange = jest.fn(); // 创建一个模拟函数
- wrapper = mount( // 挂载要测试的组件
- <Accordion onChange={onChange}>
- <Accordion.Item name='one' header='one'>
- two
- </Accordion.Item>
- <Accordion.Item name='two' header='two' disabled={true}>
- two
- </Accordion.Item>
- <Accordion.Item name='three' header='three' showIcon={false}>
- three
- </Accordion.Item>
- <Accordion.Item name='four' header='four' active={true} icons={['custom']}>
- four
- </Accordion.Item>
- </Accordion>
- );
- });
- afterEach(() => { // 在每个测试用例运行后执行的清理操作
- wrapper.unmount(); // 卸载已挂载的组件
- });
- // UI 快照测试,确保界面不会意外改变
- test('Test snapshot', () => {
- expect(toJSON(wrapper)).toMatchSnapshot(); // 比较当前组件状态的快照与预期快照
- });
- // 事件测试,点击触发 onChange 事件
- test('should trigger onChange', () => {
- wrapper.find('.qtc-accordion-item-header').first().simulate('click'); // 模拟点击事件
- expect(onChange.mock.calls.length).toBe(1); // 检查模拟函数的调用次数
- expect(onChange.mock.calls[0][0]).toBe('one'); // 检查调用时传递的参数
- });
- // 关键逻辑测试
- // 点击头部触发展开收起
- test('should expand and collapse', () => {
- wrapper.find('.qtc-accordion-item-header').at(2).simulate('click'); // 模拟特定位置的点击
- expect(wrapper.find('.qtc-accordion-item').at(2).hasClass('active')).toBeTruthy(); // 检查是否添加了特定类表示展开
- });
- // 配置 disabled 时不可展开
- test('should not trigger onChange when disabled', () => {
- wrapper.find('.qtc-accordion-item-header').at(1).simulate('click'); // 模拟点击禁用的项
- expect(onChange.mock.calls.length).toBe(0); // 检查模拟函数是否未被调用
- });
- // 对所有的属性配置进行测试
- // 是否展示头部左侧图标
- test('hide icon', () => {
- expect(wrapper.find('.qtc-accordion-item-header').at(2).children().length).toBe(2); // 检查特定位置的子元素数量
- });
- // 自定义图标
- test('custom icon', () => {
- const customIcon = wrapper.find('.qtc-accordion-item-header').at(3).children().first(); // 获取特定位置的第一个子元素
- expect(customIcon.getDOMNode().innerHTML).toBe('custom'); // 检查元素的内容
- });
- // 是否可展开多项
- test('single expand', () => {
- onChange = jest.fn(); // 重新创建模拟函数
- wrapper = mount(
- <Accordion multiple={false} onChange={onChange}>
- <Accordion.Item name='1'>1</Accordion.Item>
- <Accordion.Item name='2'>2</Accordion.Item>
- </Accordion>
- );
- wrapper.find('.qtc-accordion-item-header').at(0).simulate('click'); // 模拟点击
- wrapper.find('.qtc-accordion-item-header').at(1).simulate('click');
- expect(wrapper.find(Accordion).state().activeNames).toEqual(new Set(['2'])); // 检查状态中的活动项
- });
- test('mutiple expand', () => {
- onChange = jest.fn(); // 重新创建模拟函数
- wrapper = mount(
- <Accordion multiple={true} onChange={onChange}>
- <Accordion.Item name='1'>1</Accordion.Item>
- <Accordion.Item name='2'>2</Accordion.Item>
- </Accordion>
- );
- wrapper.find('.qtc-accordion-item-header').at(0).simulate('click'); // 模拟点击
- wrapper.find('.qtc-accordion-item-header').at(1).simulate('click');
- expect(wrapper.find(Accordion).state().activeNames).toEqual(new Set(['1', '2'])); // 检查状态中的活动项
- });
- });
复制代码 静态 Static:ESLint、Prettier等
总结
一个有效的测试策略通常会联合使用单元测试、集成测试和端到端测试,形成一个完整的测试金字塔。
在测试金字塔的底层,单元测试占据主导地位,数目最多,执行频率最高。中心层是集成测试,数目适中,验证关键组件的交互。金字塔顶端是端到端测试,数目较少,但覆盖范围最广。这种分层策略可以大概在保证测试全面性的同时,分身执行效率和成本控制。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |