前端单元测试是怎么做的?

单元测试是验证代码行为与预期一致、提升软件质量和降低开发成本的有效手段。设计单元测试涉及理解代码功能、绘制流程图并进行评审。TDD和BDD是两种常见的测试方法论。前端单元测试主要针对函数、模块和对象,常用工具有Jasmine、Qunit、Mocha和Jest等。编写测试用例时,需确保确定性,使用describe、it等结构,并利用expect进行断言。文章还给出了使用Jest和Enzyme进行组件测试的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要做单元测试

1. 执行单元测试,就是为了证明这段代码的行为和我们期望的一致

2. 进行充分的单元测试,是提高软件质量,降低开发成本的必由之路

3. 在开发人员做出修改后进行可重复的单元测试可以避免产生那些令人不快的负作用

怎么去设计单元测试

·理解这个单元原本要做什么(倒推出一个概要的规格说明(阅读那些程序代码和注释))

· 画出流程图

· 组织对这个概要规格说明的走读(Review),以确保对这个单元的说明没有基本的错误

· 设计单元测试

在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的

两个常用的单元测试方法论:

· TDD(Test-driven development):测试驱动开发

· BDD(Behavior-driven development):行为驱动开发

前端与单元测试

如何对前端代码做单元测试

通常是针对函数、模块、对象进行测试

至少需要三类工具来进行单元测试:

*测试管理工具

*测试框架:就是运行测试的工具。通过它,可以为 JavaScript 应用添加测试,从而保证代码的质量

*断言库

测试浏览器

测试覆盖率统计工具

测试框架选择

Jasmine:Behavior-Drive development(BDD)风格的测试框架,在业内较为流行,功能很全面,自带 asssert、mock 功能

Qunit:该框架诞生之初是为了 jquery 的单元测试,后来独立出来不再依赖于 jquery 本身,但是其身上还是脱离不开 jquery 的影子

Mocha:node 社区大神 tj 的作品,可以在 node 和 browser 端使用,具有很强的灵活性,可以选择自己喜欢的断言库,选择测试结果的 report

Jest:来自于 facebook 出品的通用测试框架,Jest 是一个令人愉快的 JavaScript 测试框架,专注于简洁明快。他适用但不局限于使用以下技术的项目:Babel, TypeScript, Node, React, Angular, Vue

如何编写测试用例(Jest + Enzyme)

通常测试文件名与要测试的文件名相同,后缀为.test.js,所有测试文件默认放在__test__文件夹中。

describe块之中,提供测试用例的四个函数:before()、after()、beforeEach()和 afterEach()。它们会在指定时间执行(如果不需要可以不写)。

测试文件中应包括一个或多个describe, 每个 describe 中可以有一个或多个it,每个describe中可以有一个或多个expect。

describe 称为"测试套件"(test suite),it 块称为"测试用例"(test case)。

expect就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。

所有的测试都应该是确定的。 任何时候测试未改变的组件都应该产生相同的结果。 你需要确保你的快照测试与平台和其他不相干数据无关。

基础模板

 describe('加法函数测试', () => {
    before(() => {
      // 在本区块的所有测试用例之前执行
    });
    after(() => {
      // 在本区块的所有测试用例之后执行
    });
    beforeEach(() => {
      // 在本区块的每个测试用例之前执行
    });
    afterEach(() => {
      // 在本区块的每个测试用例之后执行
    });
    it('1加1应该等于2', () => {
      expect(add(1, 1)).toBe(2);
    });
    it('2加2应该等于4', () => {
      expect(add(2, 2)).toBe(42);
    });
  });

常用的测试

组件中的方法测试:

it('changeCardType', () => {
    let component = shallow(<Card />);
    expect(component.instance().cardType).toBe('initCard');
    component.instance().changeCardType('testCard');
    expect(component.instance().cardType).toBe('testCard');
  });

模拟事件测试

通过 Enzyme 可以在这个返回的 dom 对象上调用类似 jquery 的 api 进行一些查找操作,还可以调用 setProps 和 setState 来设置 props 和 state,也可以用 simulate 来模拟事件,触发事件后,去判断 props 上特定函数是否被调用,传参是否正确;组件状态是否发生预料之中的修改;某个 dom 节点是否存在是否符合期望。

 it('can save value and cancel', () => {
    const value = 'edit';
    const { wrapper, props } = setup({
      editable: true,
    });
    wrapper.find('input').simulate('change', { target: { value } });
    wrapper.setProps({ status: 'save' });
    expect(props.onChange).toBeCalledWith(value);
  });

使用 snapshot 进行 UI 测试:

it('App -- snapshot', () => {
const renderedValue = renderer.create(<App />).toJSON();
expect(renderedValue).toMatchSnapshot();
});

真实用例分析(组件)

写一个单元测试你需要这样做

1. 看代码,熟悉待测试模块的功能和作用

2. 设计测试用例必须覆盖到组件的各种情况

3. 对错误情况的测试

通常测试文件名与要测试的文件名相同,后缀为.test.js,所有测试文件默认放在test文件夹中,一般测试文件包含下列内容:

·全局设置:一些前置配置,mock 的全局或第三方方法、进行一些重复的组件初始化工作,,当多个测试用例有相同的初始化组件行为时,可以在这里进行挂载和销毁

· UI 测试:为组件打快照,第一次运行测试命令会在目录下生成一个组件的 DOM 节点快照,在之后的测试命令中会与快照文件进行 diff 对照,避免在后面对组件进行了非期望的 UI 更改

· 关键行为:验证组件的基本行为(如:Checkbox 组件的勾选行为)

· 事件:测试各种事件的触发

· 属性:测试传入不同属性值是否得到与期望一致的结果

accordion 组件

// 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';
  import JestMock from 'jest-mock';
  import React from 'react';
  import { Accordion } from '..';
  Enzyme.configure({ adapter: new Adapter() }); // 需要根据项目的react版本来配置适配
  describe('Accordion', () => {
    // 测试套件,通过 describe 块来将测试分组
    let onChange: JestMock.Mock<any, any>; // Jest 提供的mock 函数,擦除函数的实际实现、捕获对函数的调用
    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快照测试,确保你的UI不会因意外改变
    test('Test snapshot', () => {
      // 测试用例,需要提供详细的测试用例描述
      expect(toJSON(wrapper)).toMatchSnapshot();
    });
    // 事件测试
    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']));
    });
  });

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取  

<think>嗯,用户问的是如何进行前端JavaScript单元测试,特别是使用Jest或Mocha这样的框架。首先,我需要回想一下前端单元测试的基本流程,以及这两个框架的特点。Jest和Mocha都是流行的测试框架,但它们的配置和使用方式可能有所不同。 我应该先介绍单元测试的基本概念,然后分别讲解Jest和Mocha的使用步骤。用户可能需要知道如何安装这些框架,如何编写测试用例,以及如何运行测试。此外,可能还需要提到一些相关的工具,比如断言库(比如Chai)和测试覆盖率工具(比如Istanbul)。 记得要比较Jest和Mocha的不同之处,比如Jest是零配置的,而Mocha可能需要更多的设置。还要提到Jest内置了断言和mock功能,而Mocha需要配合其他库使用。用户可能关心选择哪个框架更适合他们的项目,所以需要给出一些选择的建议,比如项目规模、是否需要开箱即用的功能等。 另外,用户可能对如何集成测试到现有的项目中感兴趣,比如与Webpack或Babel的配合。还有持续集成环境的配置,比如在GitHub Actions中运行测试。这部分内容可以增加回答的实用性。 需要注意避免过于技术性的术语,保持解释的清晰和易懂。步骤要分点说明,让用户能够一步步跟随操作。同时,要确保提供的代码示例正确无误,并且符合最佳实践,比如使用describe和it块来组织测试用例。 还要考虑用户可能的后续问题,比如如何测试异步代码、如何处理DOM操作测试(可能需要使用jsdom),或者如何生成测试覆盖率报告。在相关问题部分,可以提前列出这些可能的问题,帮助用户进一步探索。 最后,检查引用部分是否有相关的参考资料,但根据用户提供的示例,似乎没有具体的引用来源,所以可能需要忽略引用标识,或者确认是否需要添加。根据用户提供的系统指令,引用标识需要自然地添加,但如果没有具体引用内容,可能不需要添加。</think># 前端 JavaScript 单元测试实施指南 ## 一、单元测试核心流程 1. **环境搭建**:使用 Node.js 环境,通过 npm/yarn 安装测试框架 ```bash npm install --save-dev jest mocha chai ``` 2. **测试文件规范**: - 创建 `__tests__` 目录或 `*.test.js` 文件 - 遵循 `被测模块.test.js` 命名规则 3. **测试生命周期**: ```javascript beforeEach(() => { /* 初始化操作 */ }); afterEach(() => { /* 清理操作 */ }); ``` ## 二、Jest 实施方案 ### 1. 零配置启动 ```javascript // math.test.js test('adds 1 + 2 to equal 3', () => { expect(1 + 2).toBe(3); }); ``` 执行命令:`npx jest --coverage` ### 2. 核心功能 - 快照测试:`expect(tree).toMatchSnapshot();` - Mock 函数:`jest.fn().mockImplementation()` - 定时器模拟:`jest.useFakeTimers()` ### 3. 配置文件(jest.config.js) ```javascript module.exports = { testEnvironment: 'jsdom', // 支持 DOM 操作测试 transform: { '^.+\\.jsx?$': 'babel-jest' // 支持 ES6 语法 } }; ``` ## 三、Mocha 配置方案 ### 1. 基础配置 ```javascript // test/test.js const assert = require('chai').assert; describe('Array', () => { describe('#indexOf()', () => { it('当值不存在时应返回-1', () => { assert.equal([1,2,3].indexOf(4), -1); }); }); }); ``` ### 2. 扩展配置 ```bash mocha --require @babel/register --reporter spec ``` ### 3. 组合方案 - 断言库:Chai - HTTP 测试:Supertest - 覆盖率:nyc ## 四、最佳实践对比 | 特性 | Jest | Mocha | |---------------------|-------------------|-------------------| | 安装大小 | 较大(包含完整工具链) | 较小(需组合插件) | | 配置复杂度 | 零配置 | 需要手动配置 | | 异步测试 | 内置支持 | 需要 done 回调 | | 代码覆盖率 | 内置 istanbul | 需要 nyc 扩展 | | DOM 测试 | 通过 jsdom 支持 | 需要浏览器环境 | ## 五、持续集成配置示例 ```yaml # GitHub Actions 配置 name: Node CI on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: '14' - run: npm install - run: npm test ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值