欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 前端技术 > HTML >内容正文

HTML

React 之 jest 前端自动化测试

发布时间:2024/3/26 HTML 79 豆豆
生活随笔 收集整理的这篇文章主要介绍了 React 之 jest 前端自动化测试 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

一. 自动化测试简介

  • 为什么要前端自动化测试:
    自动化测试可以间接的提供代码的测试,多人协作时相互之间未知逻辑的改动等产生的未知或新问题的预警。有效避免一些未考虑到及低级的错误。

  • 自动化测试需要工作:
    自动化测试需要我们手动编写测试代码,当部分逻辑发生改变时,也需要同步更新我们的测试代码。重一定的角度上它也间接的提高了开发及维护成本。这点在实际开发运用中,大家根据实际项目情况来衡量。

  • 前段测试工具概览:
    前端测试工具纷繁复杂,大致分为测试框架, 断言库, 测试覆盖率工具等。

测试框架
测试框架的作用是提供一些方便的语法来描述测试的用例,以及对用例进行分组。
测试框架可分为两种: TDD(测试驱动开发)和 BDD(行为驱动开发)。
常见的测试框架有 Jasmine, Mocha 及 接下来我们要介绍的 Jest

断言库
断言库主要提供语义化方法,用于对参与测试的值做各种各样的判断。 这些语义化方法会返回测试的结果,要么成功,要么失败。
产概念的断言库有Should.js Chai.js 等

测试覆盖率工具
用于统计测试用例对代码的测试情况, 生成响应的报表。 比如* istanbul *

  • 关于Jest 测试框架概述
    Jest 是Facebook 出品的一个测试框架, 其一大特点是内置了常用的测试工具,比如:自带断言(expect), 测试覆盖率工具(coverage),实现了开箱即用等 。
    Jest 可以利用其特有的快照测试功能, 通过比对UI代码生成的快照文件, 实现对React等常见框架的自动化测试。
    此外,Jest 测试用例是并行执行的, 而且只执行发生改变的文件所对应的测试,提升了测试速度。

二. Jest 的实践

1. 环境搭建

这里我们主要研究jest的搭建所以,您可以通过官网安装 Create React App 来搭建一个开发环境。
接下来我们需要做如下事情:

  • 安装依赖包
    npm i jest babel-jest -D

  • 添加jest.config.js 文件

// 根目录下创建: jest.config.js// 配置文档 //https://jestjs.io/docs/zh-Hans/configuration module.exports = {// Automatically clear mock calls and instances between every testclearMocks: true,// The directory where Jest should output its coverage filescoverageDirectory: "coverage",// An array of regexp pattern strings used to skip coverage collectioncoveragePathIgnorePatterns: ["\\\\node_modules\\\\"],// An array of file extensions your modules usemoduleFileExtensions: ["js","jsx",],// A list of paths to directories that Jest should use to search for files inroots: null,// The test environment that will be used for testingtestEnvironment: "node",// The glob patterns Jest uses to detect test filestestMatch: ["**/__tests__/**/*.js?(x)",//"**/?(*.)+(spec|test).js?(x)"],// An array of regexp pattern strings that are matched against all test paths, matched tests are skippedtestPathIgnorePatterns: ["\\\\node_modules\\\\"],// A map from regular expressions to paths to transformerstransform: {"^.+\\.js$": "babel-jest"},// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformationtransformIgnorePatterns: ["\\\\node_modules\\\\"], };
  • 创建测试代码文件目录

在根目录下创建*tests* 文件夹用来存放测试脚本文件。
注: Jest 的测试脚本名形如 **.test.js 或 *.spec.js。 当执行npm run test 命令时,
它会执行当前目录下的所有 的 *.test.js 或 *.spec.js 文件, 完成测试。

  • package.json中添加命令
"scripts":{"my-test": "jest --colors --coverage" }
  • 添加对Jest 的 ES6+支持
    因为jest是基于Node 环境运行。 Node默认对ES6+语法不全支持。所以如果我们用到了ES6+语法,需要为其添加语法支持。
// 为了避免版本冲突,将babel版本全部升级为7,或者将版本全部降到6: // 即: @babel/* 7.* "@babel/core": "^7.8.4","@babel/preset-env": "^7.8.4","@babel/preset-react": "^7.8.3","babel-jest": "^24.9.0","babel-loader": "^8.0.6",

2. 用法

  • 用例的表示
    Jest 内部使用了 Jasmin2 来进行测试, 故其用例语法与 Jasmine相同。==test()==函数来描述一个测试用例。
// Demo 普通函数// src/sum.js const sum = (a, b) => {return a+b } export default sum;// __tests__/demo.test.js import sum from '../src/sum';test('adds 1 + 2 to equal 3', () => {expect(sum(1,2)).toBe(3) })

执行命令 npm run my-test 输出结果如下:

单元测试的几个指标:
%stmts 是语句覆盖率(statement coverage):是不是每个语句都执行了?
%Branch 分支覆盖率(branch coverage):是不是每个if代码块都执行了?
%Funcs 函数覆盖率(function coverage):是不是每个函数都调用了?
%Lines 行覆盖率(line coverage):是不是每一行都执行了?

  • UI 组件测试
// Demo UI 组件测试// src/commentItem.js import React from 'react'const CommentItem = (props) => (<div className={props.list.length>=1?'btn-expand':''}>{props.list.map((item, index) => {return <p key={index}>{item}</p>})}</div> ) export default CommentItem// __tests__/demo.ui.test.jsimport React from 'react' import Enzyme, { shallow } from 'enzyme' import CommentItem from '../src/commentItem' import Adapter from 'enzyme-adapter-react-16' Enzyme.configure({ adapter: new Adapter() })describe('测试评论列表项组件', () => {// 这是mocha的玩法,jest可以直接兼容it('测试评论内容小于等于200时不出现展开收起按钮', () => {const propsData = {name: 'hj',content: '测试标题',list:['l1','l2','l3']}const item = shallow(<CommentItem {...propsData} />);// 这里的断言实际上和chai的expect是很像的expect(item.find('.btn-expand').length).toBe(0);})// 这是jest的玩法,推荐用这种test('两数相加结果为两个数字的和', () => {expect(3).toBe(3);}); })

扩展
细心的同学应该注意到了,这个实例中用到了==enzyme,Adapter ==。 这里简单说下两者的作用。
(1)Enzyme 简介 传送门

(2)Adapter : 在使用 enzyme 时,需要先适配React版本。

npm i enzyme-adapter-react-16 -D //使用 import Adapter from 'enzyme-adapter-react-16' Enzyme.configure({ adapter: new Adapter() })

为了避免每次测试文件都这么写, 可以在test目录下新建一个配置文件:

import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16';Enzyme.configure({adapter: new Adapter(), });export default Enzyme;

然后在测试文件的时候引入这个配置文件即可:

import React from 'react' import Enzyme from './config/Enzyme.config'; ................ const {shallow}=Enzymedescribe('Enzyme的浅渲染测试套件', function () {it('Example组件中按钮的名字为text的值', function () {const name='按钮名'let app = shallow(<Example text={name} />)assert.equal(app.find('button').text(),name)}) })

3. Jest 之快照测试(Snapshot)

如果你的项目中还没有任何测试用例,那么使用快照测试将是一个最快的基本保障。
如果你想确保你的一些公共组件(UI)不会被意外的被修改变化,那么快照测试是一个非常有用的工具。
它的基本思想是:在测试文件目录下生成快照文件目录“snapshots/**.test.js.snap” 。 每次执行测试命令时,都会与该目录下的对应快照文件进行内容比对。 如果两个图像(内容)不匹配,则测试失败。 除非您同步更新了快照为最新版本(即测试用例中承认且同意修改更新快照内容)。

// snapshot test demo// src/Link.Snapshot.js import React from 'react';const STATUS = {HOVERED: 'hovered',NORMAL: 'normal', };export default class Link extends React.Component {constructor() {super();this.state = {class: STATUS.NORMAL,};}_onMouseEnter = () => {this.setState({class: STATUS.HOVERED});};_onMouseLeave = () => {this.setState({class: STATUS.NORMAL});};render() {return (<aclassName={this.state.class}href={this.props.page || '#'}onMouseEnter={this._onMouseEnter}onMouseLeave={this._onMouseLeave}>{this.props.children}</a>);} }// __tests__/snapshot.test.js import React from 'react'; import Link from '../src/Link.Snapshot'; import renderer from 'react-test-renderer';it('renders correctly', () => {const tree = renderer.create(<Link page="http://www.instagram.com">Instagram</Link>).toJSON();expect(tree).toMatchSnapshot(); });

执行测试命令后测试结果如下:

生成的快照文件内容如下:

// __tests__/__snapshots__/snapshot.test.js.snap// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` <aclassName="normal"href="http://www.instagram.com"onMouseEnter={[Function]}onMouseLeave={[Function]} >Instagram </a> `;

当某人不小心修改了我们的公共UI组件代码后(注:测试用例没有修改):

// src/Link.Snapshot.js........... ................ render() {return (<aclassName={this.state.class}href={ (this.props.page + 'udpate udpate !') || '#'} // 假设修改了此处, 对href 添加了自定义字符串 ‘udpate udpate !’。onMouseEnter={this._onMouseEnter}onMouseLeave={this._onMouseLeave}>{this.props.children}</a>);}.......................................

再次执行测试命令输入结果如下:

Jest 快照测试通过比对上次的快照输出文件内容,发现不一致。 输出测试失败! 表示该UI组件被修改…

如果我们统一本次的修改, 那么可以通过: npm run my-test – -u 命令来同意同步更新历史快照文件。更新完成后,则测试提示通过。

注: 快照文件应该与代码更改一起提交,并作为代码审查过程的一部分进行审核。
Jest 使用 pretty-format 对快照文件进行了处理,代码会变成可阅读的文件。

3. 常用API

  • Jest 全局方法

Describe(name, fn) : 测试套件,一组相关的测试用例。第一个参数是测试套餐的描述,第二个参数是测试用例。

const my = {name : "fynn",age : 27 } describe("my info", ()=>{test("my name", ()=>{expect(my.name).toBe("fynn")});test("my age", ()=>{expect(my.age).toBe(27)})})
  • Describe.only(name, fn)

当一个file有多个测试套件,但你只想执行其中一个测试套件,可以使用 describe.only。

const my = {name : "fynn",age : 27 } let hw = () =>"Hello World!"; describe("my info", ()=>{test("my name", ()=>{expect(my.name).toBe("fynn")});test("my age", ()=>{expect(my.age).toBe(27)})}); describe.only('hw function test suit',()=>{test('hw test',()=>{expect(hw()).toBe("Hello World!");}) })
  • Describe.skip(name, fn)

一个file中有多个测试套件,如果你想跳过某个测试套件可以使用 describe.skip

  • Test

测试用例,可以写在 describe测试套件中,也可以单独写在测试套件外面

const my = {name : "fynn",age: 27 } let hw = ()=>"Hello World!" describe("my info",()=>{test("my name",()=>{expect(my.name).toBe("fynn")});test("my age",()=>{expect(my.age).toBe(27)}) }); test("hw test",()=>{expect(hw()).toBe("Hello World!"); })
  • Test.only

有多个测试用例或测试套件,只想执行其中某一个测试用例时可以用test.only。

const my = {name : "fynn",age : 27 }; let hw = ()=>"Hello World!"; test("my name",()=>{expect(my.name).toBe("fynn"); }) test.only("hw test",()=>{expect(hw()).toBe("Hello World!"); })
  • Test.skip(name, fn)

当有多个测试用例,想跳过某个测试用例可以使用test.skip;

  • It(name,fn)

用法和test一样,不能嵌套在test中!可以嵌套在describe中

const my = {name : "fynn",age : 27 }; let hw = ()=>"Hello World!"; it("my name",()=>{expect(my.name).toBe("fynn"); }) xit("hw test",()=>{expect(hw()).toBe("Hello World!"); })
  • AfterAll(fn)

当file所有test都执行完毕后,执行afterAll中的方法。

const my = {name :"fynn",age : 27 }; test("my name",()=>{expect(my.name).toBe("fynn") }); test("my age",()=>{expect(my.age).toBe(27) }); afterAll(()=>{console.log("执行完所有test!") })
  • AfterEach(fn)

每当一个test执行完后,调用一次afterEach中的方法

const my = {name :"fynn",age : 27 }; test("my name",()=>{expect(my.name).toBe("fynn") }); test("my age",()=>{expect(my.age).toBe(27) }); afterEach(()=>{console.log("执行完一个test!") })
  • BeforeAll(fn)

在所有执行test前先调用beforeAll中的方法

const my = {name :"fynn",age : 27 }; test("my name",()=>{expect(my.name).toBe("fynn") }); test("my age",()=>{expect(my.age).toBe(27) }); beforeAll(()=>{console.log("要开始执行test了!") });
  • BeforeEach(fn)

在每个test执行前都会调用一次beforeEach中的方法

const my = {name :"fynn",age : 27 }; test("my name",()=>{expect(my.name).toBe("fynn") }); test("my age",()=>{expect(my.age).toBe(27) }); beforeEach(()=>{console.log("要开始执行一个test了!") })

其它相关API 参考如下地址:
关于Jest 官方地址 传送门

未完待续…

总结

以上是生活随笔为你收集整理的React 之 jest 前端自动化测试的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。