14

前言

本文主要是介绍基于React+Ant Design(以下用Antd表示Ant Design)的项目,在对于自己封装的,或者基于Antd封装的公共组件的自动化测试技术的选型和实践。

背景

当前前端项目越来越大,业务逻辑日益繁杂,协同开发的同事也越来越多,迭代频繁,许多页面有一些相似的功能,会复用一些组件,这些组件被剥离出来,一般放在component文件夹下,大家共同维护,这时会出现一些常见问题:

  • 保证当前组件的质量,即当前业务的正常使用
  • 在新需求下,旧的组件如果能满足新需求50%以上的功能,应当升级旧组件满足新需求,同时兼容旧业务
  • 除该组件Owner之外第二人,在修改组件的过程中,避免因为对代码的不熟悉,改出BUG
  • 一个组件多个页面复用,修改后的测试回归任务重

技术选型

目前前端整体的测试框架较为常用的有:

Jest

源自Facebook,Jest 的一个理念是提供一套完整集成的 “零配置” 测试体验。

  • 包含单元测试运行器、断言库、Mock库
  • 内置代码覆盖率报告
  • 可以与Typescript一同使用
  • 零配置,开箱即用

Mocha

仅仅是测试运行器,虽然灵活,但需要自己配置很多东西。

React项目测试选型

  • react-addons-test-utils:官方API,有些晦涩
  • Enzyme:源自Airbnb,封装了React官方测试API,类Jquery风格简洁的API, 使得Dom操作变得十分友好

综合目前市面上的轮子,我们技术选型为Jest+Enzyme

实践

例子是一个基于Antd二次封装的单选年的日期选择器,如下演示:

图片描述

代码结构如下

图片描述

其中测试相关的文件,在__test__中,后缀名为xxx.test.js的文件,在运行测试时会自动执行,__snapshots__为自动生成的页面快照。

这里可以首先简单的看一下,Jest+Enzyme的基本语法:

Jest的API更多着力于定义测试、断言、mock库

定义测试:

  • describe: 定义一个测试套件(test suite)
  • it: 定义一个测试(test)
  • beforeEach: 定义一个回调函数在每个测试之前执行
  • expect: 执行一个断言
  • jest.fn(): 创造一个mock函数

一些用于断言的方法:

  • toEqual: 验证两个值是否相同
  • toBe: 验证两个值是否 === 完全相等
  • toHaveLength:验证长度
  • toBeDefined: 验证一个值是否被定义
  • toContain: 验证一个list中是否包含某一项
  • toBeCalled: 验证一个mock函数是否被调用
  • toBeCalledWith: 验证一个mock函数是否被传入指定的参数被调用

一些用于mock的方法:

  • mockImplementation: 提供mock函数的执行
  • mockReturnValue: mock函数被调用返回一个值

Enzyme的API更多着重于渲染react组件和从dom树种检索指定的节点

下面是三种渲染组件的方法:

  • shallow: 会渲染至虚拟dom,不会返回真实的dom节点,大幅提升测试性能
  • mount: 实现Full Rendering 比如说当我们需要对DOM API交互或者你需要测试组件的整个生命周期的时候,需要使用这个方法。
  • render: 渲染出最终的html,然后利用这个html结构来进行分析处理

一些被渲染的组件检索节点的方法:

  • find: 通过匹配选择器来检索节点
  • some: 当至少有一个节点匹配选择器是返回true
  • first: 返回集合的第一个节点
  • at: 返回集合的第n个节点
  • html: 获取节点的HTML结构
  • text: 获取节点的文本

一些用于组件交互的方法:

  • simulate: 模拟一个事件
  • setProps: 设置props
  • setState: 设置state
  • props(key): 用于检索组件的props
  • state(key): 用于检索组件的state

具体的写法,index.test.js文件内容如下:

import React, { PureComponent } from 'react';
import { mount, ReactWrapper, render } from 'enzyme';
import YearPicker from '..';

import moment from 'moment';

class YearPickerDemo extends React.Component {
  state = {
    cleared: false,
    value: moment().format('YYYY'),
  };

  render() {
    return (
      <YearPicker
        showTime
        format="YYYY"
        onChange={this.onChange}
        defaultValue={moment('2015/01/01', 'YYYY')}
      />
    );
  }
}

describe('DatePicker', () => {
  it('default value', () => {
    const wrapper = mount(<YearPickerDemo/>);
    expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('2015');
  });

  it('clear value', () => {
    const wrapper = mount(<YearPickerDemo/>);
    wrapper.find('.ant-calendar-picker-clear').hostNodes().simulate('click');
    expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('');
  });

  it('set value in calendar', () => {
    const wrapper = mount(<YearPickerDemo/>);
    wrapper.find('.ant-calendar-picker-input').simulate('click');
    const triggerWrapper = mount(wrapper.find('Trigger').instance().getComponent());
    triggerWrapper.find('[title="2018"]').simulate('click');
    expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('2018');
  });
});

这里定义了3个测试内容

  • 测试默认值,即检查输入框的值是否为默认值
  • 测试清除按钮是否可用,通过模拟点击清除按钮,测试是否能按照预期清除输入框内填充的默认值
  • 测试设置值,点击输入框,弹出选择框,选择值,检查输入框中的值是否为选择的值

河马嘴不大
376 声望77 粉丝

生命比你想象的要短,多写两行代码吧!少年