React Native单元测试

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiangzhihong8/article/details/83547346

概述

所谓单元测试,就是对每个单元进行的测试,一般针对的是函数、类或单个组件,不涉及系统和集成,单元测试是软件测试的基础测试,一个完备的软件系统都会涉及到单元测试。

目前,Javascript的测试工具很多,但是针对React的测试主要使用的是Facebook推出的Jest框架,Jest是基于Jasmine的JavaScript测试框架,具有上手容易、快速、可靠的特点,是React.js默认的单元测试框架。相比其他的测试框架,Jest具有如下的一些特点:

  • 适应性:Jest是模块化、可扩展和可配置的;
  • 沙箱和快速:Jest虚拟化了JavaScript的环境,能模拟浏览器,并且并行执行;
  • 快照测试:Jest能够对React 树进行快照或别的序列化数值快速编写测试,提供快速更新的用户体验;
  • 支持异步代码测试:支持promises和async/await;
  • 自动生成静态分析结果:不仅显示测试用例执行结果,也显示语句、分支、函数等覆盖率。

环境搭建

安装Jest

首先,在项目目录下使用下面的命令安装Jest。

npm install --save-dev jest
//或者
yarn add --dev jest

如果你使用的是react-native init命令行方式来创建的RN项目,且RN版本在0.38以上,则无需手动安装,系统在生成项目的时候会自动添加依赖。

"scripts": {
 "test": "jest"
},
"jest": {
 "preset": "react-native"
}

配置Babel

现在很多的项目都使用es6及以上版本编写,为了兼容老版本,我们可以使用Babel来将es5的语法转换为es6。使用Babel前,我们需要使用如下的命令来安装Babel。

yarn add --dev babel-jest babel-core regenerator-runtime

说明:如果使用的是Babel 的version 7则需要安装babel-jest, babel-core@^7.0.0-bridge.0 和 @babel/core,安全命令如下:

yarn add --dev babel-jest babel-core@^7.0.0-bridge.0 @babel/core regenerator-runtime

然后在项目的根目录里添加 .babelrc 文件,在文件中配置如下react-native脚本内容。

{
  "presets": ["react-native"],
  "sourceMaps":true  // 用于对齐堆栈,精准的定位单元测试中的问题
}

如果是自动生成的, .babelrc 文件的配置脚本如下:

{
  "presets": ["module:metro-react-native-babel-preset"]
}

此时,需要将上面的presets配置修改为 “presets”: [“react-native”]。

完整配置

为了方便查看, 下面是package.json文件的完整配置:

{
  "name": "jestTest",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react-native": "0.55.4",
    "react": "^16.6.0",
    "react-dom": "^16.6.0"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-jest": "^23.6.0",
    "jest": "23.6.0",
    "metro-react-native-babel-preset": "0.48.3",
    "react-test-renderer": "16.5.0",
    "regenerator-runtime": "^0.12.1"
  },
  "jest": {
    "preset": "react-native",
    "transform": {
      "^.+\\.js$": "babel-jest"
    },
    "transformIgnorePatterns": [
      "node_modules/(?!(react-native)/)"
    ]
  }
}

说明:如果报AccessibilityInfo错误,请注意react-naitve的版本号,因为react-naitve的版本和其他库存在一些兼容问题,请使用0.55.4及以下稳定版本。

Cannot find module 'AccessibilityInfo' (While processing preset: "/Users/xiangzhihong029/Documents/rn/jestTest/node_modules/react-native/Libraries/react-native/react-native-implementation.js")

Enzyme

Enzyme 是 Airbnb 公司开源的测试工具库,是react-addons-test-utils的封装的产品,它模拟了 jQuery 的 API,非常直观并且易于使用和学习,提供了一些与众不同的接口和几个方法来减少测试的样板代码,方便你判断、操纵和遍历 React Components 的输出,并且减少了测试代码和实现代码之间的耦合。相比react-addons-test-utils,enzyme的API 就一目了然,下表是两个框架常用的函数的对比。
在这里插入图片描述
Enzyme提供了三种渲染方法:

shallow

shallow 方法就是对官方的 Shallow Rendering 的封装,浅渲染在将一个组件作为一个单元进行测试的时候非常有用,可以确保你的测试不会去间接断言子组件的行为。shallow 方法只会渲染出组件的第一层 DOM 结构,其嵌套的子组件不会被渲染出来,从而使得渲染的效率更高,单元测试的速度也会更快。例如:

import { shallow } from 'enzyme'

describe('Enzyme Shallow', () => {
  it('App should have three <Todo /> components', () => {
    const app = shallow(<App />)
    expect(app.find('Todo')).to.have.length(3)
  })
}

mount

mount 方法则会将 React 组件渲染为真实的 DOM 节点,特别是在依赖真实的 DOM 结构必须存在的情况下,比如说按钮的点击事件。
完全的 DOM 渲染需要在全局范围内提供完整的 DOM API, 这也就意味着它必须在至少“看起来像”浏览器环境的环境中运行,如果不想在浏览器中运行测试,推荐使用 mount 的方法是依赖于一个名为 jsdom 的库,它本质上是一个完全在 JavaScript 中实现的 headless 浏览器。
mount渲染方式的示例如下:

import { mount } from 'enzyme'

describe('Enzyme Mount', () => {
  it('should delete Todo when click button', () => {
    const app = mount(<App />)
    const todoLength = app.find('li').length
    app.find('button.delete').at(0).simulate('click')
    expect(app.find('li').length).to.equal(todoLength - 1)
  })
})

render

render 方法则会将 React 组件渲染成静态的 HTML 字符串,返回的是一个 Cheerio 实例对象,采用的是一个第三方的 HTML 解析库 Cheerio。这个 CheerioWrapper 可以用于分析最终结果的 HTML 代码结构,它的 API 跟 shallow 和 mount 方法的 API 都保持基本一致。

import { render } from 'enzyme'

describe('Enzyme Render', () => {
  it('Todo item should not have todo-done class', () => {
    const app = render(<App />)
    expect(app.find('.todo-done').length).to.equal(0)
    expect(app.contains(<div className="todo" />)).to.equal(true)
  })
})

Jest单元测试

简单示例

首先,我们在项目的根目录新建一个名为__test__的文件夹,然后编写一个组件,例如:

import React, {Component} from 'react';
import {
    Text, View,
} from 'react-native';

export default class JestTest extends Component{
    render() {
        return(<View />)
    }
}

然后,我们在__test__文件夹下编写一个名为jest.test.js的文件,代码如下:

import React from 'react';
import JestTest from '../src/JestTest';

import renderer from 'react-test-renderer';

test('renders correctly', () => {
    const tree = renderer.create(<JestTest/>).toJSON();
    expect(tree).toMatchSnapshot();
});

使用命令 “yarn jest” ,系统就会开始执行单元测试,如果没有任何错误,将会显示PASS相关的信息。
在这里插入图片描述
当然,上面的例子并没有涉及到任何的业务逻辑,只是介绍了下在React Native中如何使用Jest进行单元测试。

生成快照测试

快照测试是第一次运行测试的时候在不同情况下的渲染结果(挂载前)保存的一份快照文件,后面每次再运行快照测试时,都会和第一次的比较,除非使用“npm test – -u”命令重新生成快照文件。

为了测试快照测试,我们先新建一个带有逻辑的组件。例如:

import React, {Component} from 'react';
import {
    Text, View,
    Button
} from 'react-native';

export default class JestTest extends Component{

    constructor() {
        super();
        this.state = {liked: false};
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        return this.setState({
            liked: !this.state.liked
        });
    }

    render() {
        const text = this.state.liked ? 'like' : 'not liked';
        return (<Text onClick={this.handleClick}>
            You {text} this.Click to toggle.
        </Text>);
    }
}

上面的组件拥有三种状态,初始状态,点击状态,以及再次被点击的状态,所以在测试文件中,我们分别生成三种状态的快照,快照测试文件的代码如下:

import React from 'react';
import renderer from 'react-test-renderer';
import JestTest from "../src/JestTest";

describe('<JestTest/>', () => {
    it('Snapshot', () => {
        const component = renderer.create(<JestTest/>);

        let snapshot = component.toJSON();
        expect(snapshot).toMatchSnapshot();

        snapshot.props.onClick();
        snapshot = component.toJSON();
        expect(snapshot).toMatchSnapshot();

        snapshot.props.onClick();
        snapshot = component.toJSON();
        expect(snapshot).toMatchSnapshot()
    });
});

然后,在控制台运行yarn jest命令,就会看到在__tests___snapshots_\目录下看到快照测试,快照测试文件的代码如下:

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<JestTest/> Snapshot 1`] = `
<Text
  accessible={true}
  allowFontScaling={true}
  ellipsizeMode="tail"
  onClick={[Function]}
>
  You 
  not liked
   this.Click to toggle.
</Text>
`;

exports[`<JestTest/> Snapshot 2`] = `
<Text
  accessible={true}
  allowFontScaling={true}
  ellipsizeMode="tail"
  onClick={[Function]}
>
  You 
  like
   this.Click to toggle.
</Text>
`;

exports[`<JestTest/> Snapshot 3`] = `
<Text
  accessible={true}
  allowFontScaling={true}
  ellipsizeMode="tail"
  onClick={[Function]}
>
  You 
  not liked
   this.Click to toggle.
</Text>
`;

如果需要更新快照文件,执行yarn test – -u命令。

DOM测试

DOM测试主要测试组件生成的 DOM 节点是否符合预期,比如响应事件之后,组件的属性与状态是否符合预期。DOM 测试 依赖于官方的 TestUtil,所以需要安装react-addons-test-utils依赖库,安装的时候注意版本的兼容问题。不过在实战过程中,我发现react-addons-test-utils会报很多错误,并且官方文档也不是很友好。

这里推荐使用airbnb开源的Enzyme 脚手架,Enzyme是由 airbnb 开发的React单测工具,它扩展了React的TestUtils,并通过支持类似jQuery的find语法可以很方便的对render出来的结果做各种断言,开发体检十分友好。

生成测试报告

使用命令yarn test – --coverage就可以生成测试覆盖报告。如图:
在这里插入图片描述
同时,还会在根目录生成一个名为 coverage 的文件夹,是测试覆盖报告的网页版,包含更多,更详细的信息。

Jest基础语法

匹配器

匹配器用于测试输入输出的值是否符合预期,下面介绍一些常见的匹配器。

普通匹配器

最简单的测试值的方法就是看值是否精确匹配,使用的是toBe(),例如:

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

toBe()使用的是JavaScript中的Object.is(),属于ES6中的特性,所以不能检测对象,如果要检测对象的值的话,需要用到toEqual。

test('object assignment', () => {
  const data = {one: 1};
  data['two'] = 2;
  expect(data).toEqual({one: 1, two: 2});
});

Truthiness

在实际的测试中,有时候我们需要明确区分undefined、null和false等情况,而Jest提供的下面的一些规则可以帮我们完成上面的需求。

  • toBeNull只匹配null
  • toBeUndefined只匹配undefined
  • toBeDefine与toBeUndefined相反
  • toBeTruthy匹配任何if语句为真
  • toBeFalsy匹配任何if语句为假

数字匹配器

toBeGreaterThan():大于
toBeGreaterThanOrEqual():大于或者等于
toBeLessThan():小于
toBeLessThanOrEqual():小于或等于

E2E自动化测试

猜你喜欢

转载自blog.csdn.net/xiangzhihong8/article/details/83547346