测试环境,端到端测试

测试你的前端代码 – part4(集成测试)

2017/06/05 · 基本功技术 ·
测试

初稿出处: 澳门葡京,Gil
Tayar   译文出处:胡子大哈   

上一篇文章《测试你的前端代码 –
part3(端到端测试)》中,作者介绍了关于端到端测试的基本知识,从本文介绍集成测试(Integration
Testing)。

测试你的前端代码 – part3(端到端测试)

2017/06/05 · 基础技术 ·
测试

初稿出处: Gil
Tayar   译文出处:胡子大哈   

上一篇小说《测试你的前端代码 –
part2(单元测试)》中,小编介绍了有关单元测试的基本知识,从本文介绍端到端测试(E2E
测试)。

1.为何要利用单元测试工具?

因为代码之间的相互调用关系,又希望测试过程单元相互独立,又能健康运转,那就需求大家对被测函数的依赖函数和环境举办mock,在测试数据输入、测试执行和测试结果检查方面存在很多相似性,测试工具正是为我们在这一个方面提供了便于。

所谓单元测试也等于对各样单元举办测试,通俗的将一般针对的是函数,类或单个组件,不关乎系统和集成。单元测试是软件测试的根基测试。

React项目标单元测试

React的组件结构和JSX语法,对上一章的始末来讲举办测试突显很勉强。
React官方已经提供了2个测试工具库:react-dom/test-utils
只是用起来不够方便,于是有了一部分第③方的封装库,比如Airbnb公司的Enzyme

合并测试

笔者们早已看过了“测试光谱”中的三种测试:单元测试和端到端测试。实际工作中的测试平常是在乎那三种测试时期的,包蕴自小编在内的多数人经常把那种测试叫做集成测试。

端到端测试

在第贰片段中,大家利用 Mocha
测试了运用中最中央的逻辑,calculator模块。本文中我们将使用端到端测试整个应用,实际上是效仿了用户拥有只怕的操作进行测试。

在大家的例子中,总计器展现出来的前端即为整个应用,因为从没后端。所以端到端测试就是说直接在浏览器中运转应用,通过键盘做一比比皆是总括操作,且保证所出示的输出结果都以正确的。

是还是不是要求像单元测试那样,测试各类组合呢?并不是,大家早已在单元测试中测试过了,端到端测试不是反省有些单元是还是不是ok,而是把它们放到一起,检查依然否可以科学运转。

2.React 的标配测试工具 Jest。

Jest首要有以下特征:

      1.适应性:Jest是模块化、可扩展和可配置的。

    
2.沙箱和便捷:Jest虚拟化了JavaScript的环境,能模拟浏览器,并且并行执行

     3.快照测试:Jest可以对React
树举行快照或其他种类化数值神速编写测试,提供高效更新的用户体验。

     4.支撑异步代码测试:扶助promises和async/await

    
5.自动生成静态分析结果:不仅展现测试用例执行结果,也显得语句、分支、函数等覆盖率。

JEST比较Mocha来说,因为正如几个亮点最终胜出:

     1.和React师出同门,FB官方接济

     2.已经合并了测试覆盖率检查、mock等功用,不必要安装额外的库

    
3.文档完备,官方提供了和babel、webpack集成情形下以及异步调用的测试化解方案

     4.官方提供snapshot testing消除方案

测试项目标配备

这次测试项目是按照上一章的测试项目衍生而来,包蕴上一章讲到的Mocha和chai,那里只介绍新加的一部分模块。
花色布局图如下:

澳门葡京 1

测试项目结构图

因为是React项目,所以本来须求设置React的片段事物:

npm install --save react react-dom babel-preset-react

然后.babelrc文件内容改为

{
  "presets": [ "es2015","react" ]
}

example.jsx的情节为:

import React from 'react'

const Example=(props)=>{
    return (<div>
            <button>{props.text}</button>
        </div>)
}
export default Example

example.test.js的内容目前为空

有关术语

和不计其数 TDD
爱好者聊过未来,作者了然了他们对“集成测试”这些词有一部分不等的精通。他们以为集成测试是测试代码边界,即代码对外的接口部分。

譬如说他们代码中有 Ajax,localStorage 只怕 IndexedDB
操作,那其代码就不只怕做单元测试,那时他们会把那一个代码打包成接口,然后在做单元测试的时候
mock
那几个接口。当真正测试这一个接口的时候才称为“集成测试”。从这几个角度来说,“集成测试”就是在纯的单元测试以外,测试与表面“真实世界”相关的代码。

而自个儿和其余一些人则倾向于认为“集成测试”是将四个或五个单元测试综合起来举办测试的一种格局。通过接口把与外表有关的代码打包到一道,再
mock,只是其中的一种完毕格局。

自家的见解里,决定是或不是利用真实风貌的 Ajax 恐怕其余 I/O
操作举行集成测试(即不选用mock),取决于是还是不是可以保障测试速度充分快,并且可以稳定测试(不爆发 flaky
的意况)。倘使得以分明那样的话,那尽管用诚实意况举行合并测试就好了。但是即使很慢只怕发生不安宁测试的事态,那如故用
mock 会好一些。

在大家的事例中,计算器应用唯一的真人真事 I/O 就是操作 DOM 了,没有 Ajax
调用,所以不设有上面的题材。

须求多少端到端测试

第2付诸结论:端到端测试不需求太多。

首先个原因,要是已经由此了单元测试和购并测试,那么大概早已把富有的模块都测试过了。那么端到端测试的效能就是把持有的单元测试绑到一起举办测试,所以不需求广大端到端测试。

第2个原因,那类测试一般都很慢。假若像单元测试那样有几百个端到端测试,那运转测试将会非常慢,那就违反了3个很重大的测试原则——测试快速上报结果。

其多个原因,端到端测试的结果有时候会油但是生
flaky的处境。Flaky
测试是指日常景况下可以测试通过,不过有时会出现测试失败的情景,约等于不平静测试。单元测试大致不会冒出不安静的场合,因为单元测试经常是差不离输入,简单输出。一旦测试涉嫌到了
I/O,那么不安定测试大概就现身了。那可以减去不稳定测试呢?答案是任其自流的,可以把不安静测试出现的功能减弱到还行的品位。这可以彻底化解不平稳测试呢?或然可以,可是本身到方今还没看出过[笑着哭]。

从而为了减小我们测试中的不安定因素,尽量收缩端到端测试。13个以内的端到端测试,每一个都测试应用的第1工作流。

3.Jest + Enzyme的运用进程

1.安装

$ nam install —save-dev jest

如果急需在测试项目中采取babel,须要安装babel-jest

$ nam install —save-dev babel-jest

下一场安装enzyme

$ npm install enzyme —save-dev

一经接纳的react版本在13以上,还需要设置react-addons-test-utils

$ nam i —save-dev react-addons-test-utils

 

2.配置

JEST运转基础意义固然无需配备,可是官方照旧提供了布署选项来促成本性化须要。

package.json文件中配备jest的collectCoverageFrom参数,来内定检查有着必要测试的文本(无论源文件有没有被测试文件使用到)

coverageThreshold 参数来配置测试覆盖率。

 

jest.config.js 配置jest:

module.exports = {

  bail: true, //遇上 test feature, 则Stop running test, 暗许值是false

  cacheDirectory: ‘./node_测试环境,端到端测试。modules/.cache’, //测试缓存数据的蕴藏地点

  testEnvironment: ‘jsdom’, //default brower-like enviroment,
假使你搭建了三个node service node-like enviroment

  coverageThreshold: { //测试覆盖率, 阈值不满意,就赶回测试失利

    global: {

      branches: 90,

      functions: 90,

      lines: 90,

      statements: 90,

    },

  },

  coveragePathIgnorePatterns: [ //该路径下的测试,忽略在测试覆盖率上

    ‘build’,

    ‘<rootDir>/src/shared/libs/url/’,

  ],

  testRegex: ‘test/.*\\.jsx?$’, //要测试的文件目录及后缀

  testPathIgnorePatterns: [ //忽略该路线的公文测试

    ‘<rootDir>/node_modules/’,

    ‘<rootDir>/build/’,

    ‘<rootDir>/scripts/’,

    ‘<rootDir>/api/’,

    ‘<rootDir>/test/setup.js’,

    ‘__mocks__’,

  ],

  moduleFileExtensions: [”, ‘json’, ‘js’, ‘jsx’, ‘less’],
//测试模块中用到的文本的后缀名配置

  modulePaths: [‘<rootDir>/src’, ‘<rootDir>’],

  moduleNameMapper: {  //与测试毫无干系的财富文件同意mock 掉,那样在import
的时候就不会真正引入这么些文件

    ‘^import?’: ‘<rootDir>/build/jestImportMock.js’,

    ‘\\.(css|less|gif|jpg|jpeg|png)$’:
‘<rootDir>/build/jestStyleMock.js’,

  },

  setupFiles: [‘<rootDir>/test/setup.js’],
//给逐个测试文件添加额外的安插

  transformIgnorePatterns: [ //测试进程不改变满足配置的文书

    ‘<rootDir>/node_modules/(?!(react-aaui|tempest\\.js)/)’,

    ‘babel-runtime’,

  ],

}

 

Enzyme 的装置与配置

npm install --save-dev enzyme

而enzyme还须要依照React的本子安装适配器,适配器对应表如下:

Enzyme Adapter Package React semver compatibility
enzyme-adapter-react-16 ^16.0.0
enzyme-adapter-react-15 ^15.5.0
enzyme-adapter-react-15.4 15.0.0-0 – 15.4.x
enzyme-adapter-react-14 ^0.14.0
enzyme-adapter-react-13 ^0.13.0

那就是说因为我们设置的React版本为^16.2.0
据此要求安装:

npm install --save-dev enzyme-adapter-react-16

mock DOM

这就引出了一个标题:在合龙测试中是不是需要 mock
DOM?重新思考一下方面小编说的正经,使用真实 DOM
是还是不是会使测试变慢呢,答案是会的。使用真实 DOM
意味着要用浏览器,用浏览器意味着测试速度变慢,测试变的不安宁。

那么是否要么只能够尽量把操作 DOM
的代码分离出来,要么只能够采纳端到端测试了啊?其实那二种方法都倒霉。还有另一种缓解方案:jsdom。二个老大棒的包,用它自个儿的话说:这是在
NodeJS 中完成的 DOM。

它真的相比较好用,可以运营在 Node 环境下。使用 JSDom,你可以不把 DOM 当做
I/O 操作。那一点越发重大,因为要把 DOM
操作以前端代码中分离出来万分不便(实际工作中大致不容许完全分开)。作者猜
JSDom 的落地就是因为这些原因:使得在 Node 中也得以运作前端测试。

大家来看一下它的做事原理,和未来一致,要求有发轫化代码和测试代码。这一次我们先看测试代码。可是业内看代码此前请先接受自个儿的歉意。

写端到端测试代码

好了,废话不多说,开端介绍写端到端代码。首先要求常备不懈好两件事情:1.
一个浏览器;2. 运营前端代码的服务器。

因为要动用 Mocha 进行端到端测试,就和从前单元测试一样,要求先对浏览器和
web 服务器进行一些陈设。使用 Mocha 的
before 钩子设置先导化状态,使用
after钩子清理测试后情形。before 和 after
钩子分别在测试的开端和终止时运营。

上边一起来看下 web 服务器的装置。

4.驾驭React官方测试工具库

react测试可以分成测试DOM结构 和测试Action和Reducer

React官方测试工具库提供三种测试格局:

1.Shallow Rendering 测试虚拟DOM的艺术 

Shallow Rendering
(浅渲染)指的是,将贰个零部件渲染成虚拟DOM对象,可是只渲染第1层,不渲染全体子组件,所以处理速度分外快。它不须要DOM环境,因为一直没有加载进DOM。

import TestUtils from ‘react-addons-test-utils’;

function shallowRender(Component) {

  const renderer = TestUtils.createRenderer();

  renderer.render(<Component/>);

  return renderer.getRenderOutput();

}

Shallow Rendering
函数,该函数再次回到的就是一个浅渲染的虚拟DOM对象。只有一层,不重返子组件。

2.DOM Rendering 测试真实DOM的法子

法定测试工具库的第贰种测试方法,是将零件渲染成真正的DOM节点,再开展测试。那时就须要调用renderIntoDocument
方法。

import TestUtils from ‘react-addons-test-utils’;

import App from ‘../app/components/App’;

const app = TestUtils.renderIntoDocument(<App/>);

renderIntoDocument
方法须要存在3个真真的DOM环境,否则会报错。由此,测试用例之中,DOM环境(即window,
document 和 navigator 对象)必须是存在的

 

Enzyme库对法定测试库举办了打包,它提供二种方法:

import { shallow, mount, render } from ‘enzyme’

shallow 重临组件的浅渲染,对法定shallow rendering 举行打包

const wrapper = shallow(<Counter {…props} />)

expect(wrapper.find(‘button’).exists()).toBeTruthy()

shallow 重临Counter 的浅渲染,然后调用find 方法寻找 button 成分

有关find方法,有三个注意点,就是它只协理容易选拔器,稍微复杂的一些的CSS拔取器都不帮助。

render
方法将React组件渲染成静态的HTML字符串,然后分析那段HTML代码的结构,重回贰个目标。它跟shallow方法丰裕像,首要的两样是应用了第2方HTML解析库Cheerio,它回到的是三个Cheerio实例对象。

const wrapper = render(<Counter {…props} />)

expect(wrapper.find(‘button’).exists()).toBeTruthy()

render方法与shallow方法的API基本是平等的。

Enzyme的筹划就是,让不相同的最底层处理引擎,都具备同等的API

 

mount 方法用于将React组件加载为实际DOM节点。

const wrapper = mount(<Counter arithmetic={arithmetic})

 wrapper.find(‘button’).simulate(‘click’)

 

Enzyme的一部分API,你能够从中了然它的大概用法。

.get(index):再次回到内定地点的子组件的DOM节点

.at(index):重返指定地点的子组件

.first():重临第③身材组件

.last():重临最终2个子组件

.type():重回当前组件的品种

.text():再次来到当前组件的文件内容

.html():再次来到当前组件的HTML代码格局

.props():重回根组件的保有属性

.prop(key):重返根组件的钦点属性

.state([key]):重回根组件的状态

.setState(nextState):设置根组件的情景

.setProps(nextProps):设置根组件的习性

 

toMatchSnapshot方法会去帮您比较本次即将变化的协会与上次的分别

 

测试 异步action

他的I/O只怕凭借store.getState(),自个儿又会借助异步中间件,那类使用原生js测试起来相比较辛勤,我们的目标可以设定为:当大家接触3个action后,它经历了一个圈异步最后store.getAction中的action得到的多少和大家预料一致。由此大家须求用到八个库:redux-mock-store
和 nock。

const mockStore = configureStore([thunk, promiseMiddleware])
//配置mock 的store,让她们有同一的middleware

afterEach(() => 

 nock.cleanAll()

)  //每执行完一个测试后,清空nock

 

const store = mockStore({

   router: {

      location: ‘/’,

    },

})  //以我们约定的开头state创设store,控制 I/O 着重

 

const data = [

//接口重临的消息

{…

},

]

 

nock(API_HOST) //拦截请求再次来到的response

.get(`/api/…`) //拼接路由,需求在test.js中配备测试路径

.reply(200, {code: 0, data})

return store.dispatch(actions.getAll()).then(() =>
expect(store.getActions()).toMatchSnapShot())

 

1.用nock来mock拦截http请求结果,并赶回大家加以的response

2.用redux-mock-store 来mock store
的生命周期,需求事先把middleware配成和系列一律

3.describe会包罗部分生命周期的api,比如整个测试开首做什么,单个测试截止做吗api,那里每执行完3个测试就清空nock

4.用了jest中的toMatchSnapShot api 来判断三个原则是还是不是一致。

本来你要写成 expect(store.getActions()).toEqual({data …}) 你需要把equal
里的事物都描写具体,而toMatchSnapshot

可在当前目录下生成三个snapshot
,专门存放当前结果,写测试时看一眼是预期的就commit。假如改坏了,函数就不匹配snapshot了。

Enzyme 的使用

后日上马用Enzyme为example.jsx编写测试代码:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

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

如上面代码所示,在应用Enzyme 前需求先适配React对应的版本

Enzyme.configure({ adapter: new Adapter() })

而为了防止每一种测试文件都那样写,可以再test目录下新建3个配备文件:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({
  adapter: new Adapter(),
});

export default Enzyme;

下一场测试文件的时候只需求引用那个布局文件即可:

import {assert} from 'chai'
import React from 'react'
import Enzyme from './config/Enzyme.config';
import Example from '../src/example'

const {shallow}=Enzyme

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

歉意

这一某个是其一测试体系作品中绝无仅有运用钦点框架的部分,这某个行使的框架是
React。采取 React
并不是因为它是最好的框架,笔者坚决地觉得并未所谓最好的框架,作者竟然认为对于指定的现象也没有最好的框架。作者深信不疑的是对于个体来讲,唯有最合适,用着最顺手的框架。

而我动用着最顺手的框架就是 React,所以接下去的代码都是 React
代码。可是此地依旧说喜宝(Hipp)下,前端集成测试的 jsdom
化解方案能够适用于拥有的主流框架。

ok,今后回去正题。

设置 Web 服务器

安插1个 Node Web 服务器,首先想到的就是
express了,话不多说,直接上代码:

JavaScript

let server before((done) = > { const app = express() app.use(‘/’,
express.static(path.resolve(__dirname, ‘../../dist’))) server =
app.listen(8080, done) }) after(() = > { server.close() })

1
2
3
4
5
6
7
8
9
10
let server
before((done) = > {
    const app = express()
    app.use(‘/’, express.static(path.resolve(__dirname, ‘../../dist’)))
    server = app.listen(8080, done)
})
after(() = > {
    server.close()
})

代码中,before 钩子中开创多个 express 应用,指向 dist
文件夹,并且监听 8080 端口,停止的时候在 after 钩子中关闭服务器。

dist 文件夹是何等?是大家打包 JS 文件的地点(使用 Webpack打包),HTML
文件,CSS 文件也都在此间。可以看一下 package.json 的代码:

JavaScript

{ “name”: “frontend-testing”, “scripts”: { “build”: “webpack && cp
public/* dist”, “test”: “mocha ‘test/**/test-*.js’ && eslint test
lib”, … },

1
2
3
4
5
6
7
{
      "name": "frontend-testing",
      "scripts": {
        "build": "webpack && cp public/* dist",
        "test": "mocha ‘test/**/test-*.js’ && eslint test lib",
    …
      },

对此端到端测试,要记得在举办 npm test 之前,先执行
npm run build。其实这么很不便利,想转手以前的单元测试,不必要做如此复杂的操作,就是因为它可以一向在
node 环境下运营,既不用转译,也不用包装。

鉴于完整性考虑,看一下 webpack.config.js 文件,它是用来报告 webpack
如何处理打包:

JavaScript

module.exports = { entry: ‘./lib/app.js’, output: { filename:
‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }, … }

1
2
3
4
5
6
7
8
module.exports = {
    entry: ‘./lib/app.js’,
    output: {
        filename: ‘bundle.js’,
        path: path.resolve(__dirname, ‘dist’)
    },
    …
}

上边的代码指的是,Webpack 会读取 app.js 文件,然后将 dist
文件夹中有所应用的公文都打包到 bundle.js 中。dist
文件夹会同时选取在生育条件和端到端测试环境。那里要小心壹个很重点的事体,端到端测试的运作条件要尽或者和生育环境保持一致。

5.测试注意事项

1.拆分单元,关怀输入输出,忽略中间进程。dom测试时只用确保正确调用了action函数,传参正确,而不用关爱函数调用结果,置于action处理结果,reducer中对state的变动这个都留下action和reducer本身的单元测试区测。不要想着测试整个大功能的流水线,不要有闭环的思索,单元测试须要保险的近年来单元朔常,对于逐个单元模块输入输出都毋庸置疑,理论串联后一路利用闭环时也会不错。

 

2.有余情状的测试覆盖,若是无法确保测试的周密性,逐个情景都覆盖到,那么那几个测试就是个不敢依靠的不圆满的测试。当然在其实项目中,只怕因为日子、能源等题材,无法保证逐个景况都测试到,而只测试紧要的始末,那时候要完结心里有数,反正小编是对此逐个测试都写注释的,交代清楚测试覆盖了怎么样,还有哪些没有掩盖,需求任何手段保证平稳。

 

3.爱抚该关切的,毫无干系主要的mock掉。css、图片那种mock掉,http请求mock掉

 

4.本来不便利测试的代码依然须要修改的,并不可以为了原代码稳定不变,在测试时不敢动原代码。譬如函数不纯,没有重回值等。

 

Enzyme 的利用之浅渲染shallow

上边的例子中就用到浅渲染shallow 。
Shallow
Rendering(浅渲染)指的是,将2个零件渲染成虚拟DOM对象,可是只渲染第叁层,不渲染全部子组件,所以处理速度非凡快。它不需求DOM环境,因为一向没有加载进DOM。
shallow的函数输入组件,重返组件的浅渲染结果,而回到的结果可以用接近jquery的样式取得组件的信息。
运行

npm test 

也就是:

mocha --require babel-core/register

赢得以下结果:

澳门葡京 2

浅渲染测试运转结果

浅渲染测试与子组件的有关的代码:

至今修改大家的例子,新加三个sub.jsx:

import React from 'react'

const Sub=(props)=>{
    return ({props.text})
}
export default Sub

在原本的example.jsx中采用Sub,形成嵌套:

import React from 'react'
import Sub from './sub'

const Example=(props)=>{
    return (<div>
            <button>{props.text}</button>
            <Sub text={props.text}  />
        </div>)
}
export default Example

应用shadow测试子组件的代码:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow,mount}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme shallow的浅渲染(Shallow Rendering)中', function () {
  it('Example组件中按钮的名字为子组件Sub中span的值', function () {
    const name='按钮名'
    let app = shallow(<Example text={name} />)
    const buttonObj=app.find('button')
    const spanObj=app.find('span')

    console.info(`查找到button的个数:${buttonObj.length}`)
    console.info(`查找到span的个数:${spanObj.length}`)

    assert.equal(buttonObj.text(),spanObj.text())
  })
})

测试结果为:

澳门葡京 3

浅渲染测试子组件的结果

如上图所示,shallow所得到的浅渲染对象中差找不到子组件Sub的span元素。
为了化解地点那种情景,Enzyme给出的缓解方案为用mount完结 Full DOM
Rendering。

使用 Jsdom

JavaScript

const React = require(‘react’) const e = React.createElement const
ReactDom = require(‘react-dom’) const CalculatorApp =
require(‘../../lib/calculator-app’) … describe(‘calculator app
component’, function () { … it(‘should work’, function () {
ReactDom.render(e(CalculatorApp), document.getElementById(‘container’))
const displayElement = document.querySelector(‘.display’)
expect(displayElement.textContent).to.equal(‘0’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const React = require(‘react’)
const e = React.createElement
const ReactDom = require(‘react-dom’)
const CalculatorApp = require(‘../../lib/calculator-app’)
    …
describe(‘calculator app component’, function () {
        …
    it(‘should work’, function () {
        ReactDom.render(e(CalculatorApp), document.getElementById(‘container’))
        const displayElement = document.querySelector(‘.display’)
        expect(displayElement.textContent).to.equal(‘0’)

只顾看第 10 – 14 行,首先 render 了 CalculatorApp 组件,这几个操作同时也
render 了 DisplayKeypad。第 12 和 14 行测试了 DOM
中计算器的来得是或不是是 0(开首化状态下)。

位置的代码是可以运维在 Node 下的,注意到个中用的是
document。作者先是次采纳它的时候特意好奇。全局变量 document
是1个浏览器变量,竟然可以接纳在 NodeJS
中。在那不难的几行代码背后有着大批量的代码支撑着,那几个 jsdom
代码大概是一揽子地落到实处了浏览器的机能。所以那边作者要多谢 Domenic
Denicola, Elijah
Insua
和为以此工具包做过进献的人们。

澳门葡京 4

第 10 行中也拔取了 document(调用 ReactDom 来渲染组件),在 ReactDom
常常会采用它。那么在哪个地方创立的那么些全局变量呢?在测试中开创的,见上面代码:

JavaScript

before(function () { global.document = jsdom(`<!doctype
html><html><body><div
id=”container”/></div></body></html>`)
global.window = document.defaultView }) after(function () { delete
global.window delete global.document })

1
2
3
4
5
6
7
8
9
before(function () {
        global.document = jsdom(`<!doctype html><html><body><div id="container"/></div></body></html>`)
        global.window = document.defaultView
      })
 
    after(function () {
        delete global.window
        delete global.document
      })

代码中开创了1个简练的 document,把我们的零部件挂在一个简单 div
上。同时还创办了壹个 window,其实大家并不需要它,可是 React 需求。最终在
after 中清理全局变量。

documentwindow
一定要设置成全局的啊?滥用全局变量不论理论和施行的角度都不是个好习惯。假若它们是大局的,那这几个集成测试就不可能和其他的融会测试并行运营(那里对
ava
的用户表示对不起),因为它们会互相覆写全局变量,导致结果错误。

只是,它们必须求设置成全局的,React 和 ReactDOM 须要 document
window 是大局的,不收受把她们以参数的款型传递。恐怕等 React fiber
出来就足以了?恐怕吧,然方今后咱们还必须求把 documentwindow
设置成全局的。

安装浏览器

现行我们曾经设置完了后端,应用已经有了服务器提供劳务了,将来要在浏览器中运作大家的计算器应用。用如何包来驱动自动执行顺序吗,作者不时应用
selenium-webdriver,那是2个很流行的包。

第壹看一下怎么样运用驱动:

JavaScript

const { prepareDriver, cleanupDriver } =
require(‘../utils/browser-automation’) //… describe(‘calculator app’,
function () { let driver … before(async() = > { driver = await
prepareDriver() }) after(() = > cleanupDriver(driver)) it(‘should
work’, async function () { await driver.get(”)
//… }) })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const {
    prepareDriver, cleanupDriver
} = require(‘../utils/browser-automation’)
//…
describe(‘calculator app’, function () {
    let driver
        …
    before(async() = > {
        driver = await prepareDriver()
    })
    after(() = > cleanupDriver(driver))
    it(‘should work’, async
    function () {
        await driver.get(‘http://localhost:8080’)
        //…
    })
})

before 中,准备好驱动,在 after
中把它清理掉。准备好驱动后,会自动运维浏览器(Chrome,稍后会看出),清理掉以往会关闭浏览器。那里注意,准备驱动的经过是异步的,再次来到多少个promise,所以大家运用 async/await
成效来使代码看起来更美妙(Node7.7,第1个地面协助 async/await 的版本)。

末尾在测试函数中,传递网址:http:/localhost:8080,如故利用 await,让
driver.get 成为异步函数。

你是否有好奇 prepareDrivercleanupDriver
函数长什么吗?一起来看下:

JavaScript

const webdriver = require(‘selenium-webdriver’) const chromeDriver =
require(‘chromedriver’) const path = require(‘path’) const
chromeDriverPathAddition = `: $ { path.dirname(chromeDriver.path) }`
exports.prepareDriver = async() = > { process.on(‘beforeExit’, () =
> this.browser && this.browser.quit()) process.env.PATH +=
chromeDriverPathAddition return await new webdriver.Builder()
.disableEnvironmentOverrides() .forBrowser(‘chrome’) .setLoggingPrefs({
browser: ‘ALL’, driver: ‘ALL’ }) .build() } exports.cleanupDriver =
async(driver) = > { if (driver) { driver.quit() } process.env.PATH =
process.env.PATH.replace(chromeDriverPathAddition, ”) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const webdriver = require(‘selenium-webdriver’)
const chromeDriver = require(‘chromedriver’)
const path = require(‘path’)
const chromeDriverPathAddition = `: $ {
    path.dirname(chromeDriver.path)
}`
exports.prepareDriver = async() = > {
    process.on(‘beforeExit’, () = > this.browser && this.browser.quit())
    process.env.PATH += chromeDriverPathAddition
    return await new webdriver.Builder()
        .disableEnvironmentOverrides()
        .forBrowser(‘chrome’)
        .setLoggingPrefs({
        browser: ‘ALL’,
        driver: ‘ALL’
    })
        .build()
}
exports.cleanupDriver = async(driver) = > {
    if (driver) {
        driver.quit()
    }
    process.env.PATH = process.env.PATH.replace(chromeDriverPathAddition, ”)
}

可以看来,下边那段代码很笨重,而且不得不在 Unix
系统上运行。理论上,你可以不用看懂,间接复制/粘贴到你的测试代码中就可以了,那里小编如故长远讲一下。

前两行引入了 webdriver 和大家使用的浏览器驱动 chromedriver。Selenium
Webdriver 的做事原理是经过 API(第叁行中引入的
selenium-webdriver)调用浏览器,这倚重于被调浏览器的驱动。本例中被调浏览器驱动是
chromedriver,在其次行引入。

chrome driver 不需求在机械上装了 Chrome,实际上在你运转 npm install
的时候,已经装了它自带的可实施 Chrome 程序。接下来 chromedriver
的目录名急需添加进环境变量中,见代码中的第 9
行,在清理的时候再把它删掉,见代码中第 22 行。

安装了浏览器驱动将来,大家来设置 web driver,见代码的 11 – 15 行。因为
build 函数是异步的,所以它也采用
await。到近期终止,驱动部分就早已安装截至了。

Enzyme 的利用之mount

mount方法用于将React组件加载为实际DOM节点。
不过真正DOM需求1个浏览器环境,为了化解这几个标题,大家得以用到jsdom.
上边是jsdom的法定介绍:

jsdom is a pure-JavaScript implementation of many web standards,
notably the WHATWG
DOM
and
HTML
Standards, for use with Node.js. In general, the goal of the project
is to emulate enough of a subset of a web browser to be useful for
testing and scraping real-world web applications.

也等于说大家可以用jsdom模拟二个浏览器环境去加载真实的DOM节点。
首先安装jsdom:

npm install --save-dev jsdom

然后在test目录下新建3个文件setup.js:

import jsdom from 'jsdom';
const { JSDOM } = jsdom;

if (typeof document === 'undefined') {
    const dom=new JSDOM('<!doctype html><html><head></head><body></body></html>');
    global.window =dom.window;
    global.document = global.window.document;
    global.navigator = global.window.navigator;
}

终极修改我们的package.json中的测试脚本为:

 "scripts": {
    "test": "mocha --require babel-core/register --require ./test/setup.js"
  }

这么在运营npm
test
时,会先用babel解析js,然后再实施setup.js中的代码,给global对象模拟二个浏览器环境。

在布局好模拟浏览器环境后,修改测试代码为:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow,mount}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme mount的DOM渲染(Full DOM Rendering)中', function () {
  it('Example组件中按钮的名字为子组件Sub中span的值', function () {
    const name='按钮名'
    let app = mount(<Example text={name} />)

    const buttonObj=app.find('button')
    const spanObj=app.find('span')

    console.info(`查找到button的个数:${buttonObj.length}`)
    console.info(`查找到span的个数:${spanObj.length}`)

    assert.equal(buttonObj.text(),spanObj.text())
  })
})

运行

npm test

结果为:

澳门葡京 5

mount的Full DOM Rendering测试结果

如上,在mount得到的结果中得以找到子组件的要素

事件处理

余下的测试代码怎么写吗,看上面代码:

JavaScript

ReactDom.render(e(CalculatorApp), document.getElementById(‘container’))
const displayElement = document.querySelector(‘.display’)
expect(displayElement.textContent).to.equal(‘0’) const digit4Element =
document.querySelector(‘.digit-4’) const digit2Element =
document.querySelector(‘.digit-2’) const operatorMultiply =
document.querySelector(‘.operator-multiply’) const operatorEquals =
document.querySelector(‘.operator-equals’) digit4Element.click()
digit2Element.click() operatorMultiply.click() digit2Element.click()
operatorEquals.click() expect(displayElement.textContent).to.equal(’84’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ReactDom.render(e(CalculatorApp), document.getElementById(‘container’))
const displayElement = document.querySelector(‘.display’)
expect(displayElement.textContent).to.equal(‘0’)
const digit4Element = document.querySelector(‘.digit-4’)
const digit2Element = document.querySelector(‘.digit-2’)
const operatorMultiply = document.querySelector(‘.operator-multiply’)
const operatorEquals = document.querySelector(‘.operator-equals’)
digit4Element.click()
digit2Element.click()
operatorMultiply.click()
digit2Element.click()
operatorEquals.click()
expect(displayElement.textContent).to.equal(’84’)

测试中任重(英文名:rèn zhòng)而道远完结的是用户点击 “42 * 2 = ”,结果应该是出口 “84”。那里拿到element 使用的是闻名的 querySelector 函数,然后调用 click
点击。还是可以创设事件,然后手动调度,见上边代码:

JavaScript

var ev = new Event(“keyup”, …); document.dispatchEvent(ev);

1
2
var ev = new Event("keyup", …);
document.dispatchEvent(ev);

此地有内置的 click 函数,所以大家直接使用就好了。就是如此简单!

乖巧的您可能曾经意识了,这一个测试和前面的端到端测试实在是同一的。不过注意这一个测试要快
10 倍以上,并且实际它是一路的,代码也更便于写,可读性也更好。

不过即使都如出一辙的话,那须求继续测试干嘛?因为那是个示范项目嘛,并不是实际上项目。这几个种类里面只有七个零部件,所以端到端测试和持续测试是一致的。假设是在事实上项目中,端到端测试只怕包括了诸八个单元,而继续测试只含有少量单元,比如含有
十三个单元。所以其实项目中唯有多少个端到端测试,而恐怕带有了成百上千个持续测试。

测试吧!

设置完驱动今后,该看一下测试的代码了。完整的测试代码在那边,上边列出部分代码:

JavaScript

// … const retry = require(‘promise-retry’) // … it(‘should work’,
async function () { await driver.get(”) await
retry(async() = > { const title = await driver.getTitle()
expect(title).to.equal(‘Calculator’) }) //…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// …
const retry = require(‘promise-retry’)
// …
it(‘should work’, async
function () {
    await driver.get(‘http://localhost:8080’)
    await retry(async() = > {
        const title = await driver.getTitle()
        expect(title).to.equal(‘Calculator’)
    })
    //…

这里的代码调用总结器应用,检查采用题目是或不是 “Calculator”。代码中第 6
行,给浏览器赋地址:http://localhost:8080,记得要运用 await。再看第拾 行,调用浏览器并且重回浏览器的标题,在第 10 行中与预期的标题进行比较。

此地还有二个标题,这里引入了 promise-retry
模块举办重试,为何须求重试?原因是这么的,当我们报告浏览器执行某吩咐,比如固定到三个U翼虎L,浏览器会去实践,不过是异步执行。浏览器执行的可怜快,那时候对于开发人士来讲,确切地了解浏览器“正在实施”,要比单纯掌握三个结出更主要。正是因为浏览器执行的尤其快,所以要是不重试的话,很不难被
await 所愚弄。在后面的测试中 promise-retry
也会平日使用,那就是怎么在端到端测试中需要重试的缘由。

Enzyme 的采纳之render

而Enzyme还提供了二个不需要jsdom模拟条件解决子组件测试的方法:render
Enzyme的render函数拿到的结果被称为Static Rendered
Markup,以下为法定的牵线

enzyme’s render function is used to render react components to
static HTML and analyze the resulting HTML structure.
render returns a wrapper very similar to the other renderers in
enzyme,
mount
and
shallow;
however, render uses a third party HTML parsing and traversal
library
Cheerio.
We believe that Cheerio handles parsing and traversing HTML extremely
well, and duplicating this functionality ourselves would be a
disservice.

趣味就是说render会依据react组件得到三个静态HTML文本结果,借助一个第②方的HTML解析库Cheerio去生成2个看似于mount和shallow得到的包装对象。

修改测试代码为:

import {assert} from 'chai'
import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Example from '../src/example'

const {shallow,mount,render}=Enzyme

Enzyme.configure({ adapter: new Adapter() })

describe('Enzyme render的静态HTML渲染(Static Rendered Markup)中', function () {
  it('Example组件中按钮的名字为子组件Sub中span的值', function () {
    const name='按钮名'
    let app = render(<Example text={name} />)

    const buttonObj=app.find('button')
    const spanObj=app.find('span')

    console.info(`查找到button的个数:${buttonObj.length}`)
    console.info(`查找到span的个数:${spanObj.length}`)

    assert.equal(buttonObj.text(),spanObj.text())
  })
})

测试结果为:

澳门葡京 6

render测试结果

总结

正文中至关紧要介绍了什么样:

  • 介绍了应用 jsdom
    方便地成立全局变量 documentwindow
  • 介绍了哪些接纳 jsdom 测试应用;
  • 介绍了,测试就是那样简单^_^。

    1 赞 收藏
    评论

澳门葡京 7

测试 Element

来看测试的下一阶段,测试成分:

JavaScript

const { By } = require(‘selenium-webdriver’) it(‘should work’, async
function () { await driver.get(”) //… await
retry(async() = > { const displayElement = await
driver.findElement(By.css(‘.display’)) const displayText = await
displayElement.getText() expect(displayText).to.equal(‘0’) }) //…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const {
    By
} = require(‘selenium-webdriver’)
it(‘should work’, async
function () {
    await driver.get(‘http://localhost:8080’)
    //…
    await retry(async() = > {
        const displayElement = await driver.findElement(By.css(‘.display’))
        const displayText = await displayElement.getText()
        expect(displayText).to.equal(‘0’)
    })
    //…

下贰个要测试的是开端化状态下所出示的是不是“0”,那么首先就必要找到控制展现的 element,在大家的例子中是
display。见第 7 行代码,webdriver 的 findElement
方法重回大家所要找的要素。可以经过 By.id或者 By.css
再或者其余找成分的措施。这里自身使用
By.css,它很常用,其它提一句 By.javascript 也很常用。

(不知情您是不是注意到,By 是由最上边的 selenium-webdriver 所引入的)

当大家获得到了 element 未来,就足以应用 getText()(还足以采纳其余操作
element
的函数),来取得元素文本,并且检查它是或不是和预期一样,见第7 行。对了,不要遗忘:

澳门葡京 8

shallow ,render和mount的频率相比较

空口无凭,直接上测试代码:

it('测试 shallow 500次', () => {
  for (let i = 0; i < 500; i++) {
    const nav = shallow(<Nav />)
    assert.equal(nav.find('a').text(), '首页')
  }
})

it('测试render500次', () => {
  for (let i = 0; i < 500; i++) {
    const nav = render(<Nav />)
    assert.equal(nav.find('a').text(), '首页')
  }
})

it('测试mount500次', () => {
  for (let i = 0; i < 500; i++) {
    const nav = mount(<Nav />)
    assert.equal(nav.find('a').text(), '首页')
  }
})

结果:

澳门葡京 9

shallow测试结果

澳门葡京 10

render测试结果

澳门葡京 11

mount测试结果

结果阐明:
shallow果然最快,那是一定的,不过因为shallow的局限性,我们大概更想领悟render和mount的作用。
事实讲明,render的频率是mount的两倍。
有人只怕要猜忌你为啥不将次数弄更大一些,因为在设定为一千次的地方下mount直接超时了,相当于跨越了mocha的默许执行时间范围3000ms。
那就是说难点来了,mount存在的价值是哪些,render就可以测试子组件,render还不须求jsdom和附加的计划。
自然是有价值的,shallow和mount因为都以dom对象的缘由,所以都以可以效仿交互的,比如

 const nav = mount(<Nav />)
 nav.find('a').simulate('click')

而render是不或然的。

测试 UI

当今该来从 UI
层面测试应用了,点击数字和操作符,测试统计器是或不是依据预期的运维:

JavaScript

const digit4Element = await driver.findElement(By.css(‘.digit-4’)) const
digit2Element = await driver.findElement(By.css(‘.digit-2’)) const
operatorMultiply = await
driver.findElement(By.css(‘.operator-multiply’)) const operatorEquals =
await driver.findElement(By.css(‘.operator-equals’)) await
digit4Element.click() await digit2Element.click() await
operatorMultiply.click() await digit2Element.click() await
operatorEquals.click() await retry(async() = > { const displayElement
= await driver.findElement(By.css(‘.display’)) const displayText = await
displayElement.getText() expect(displayText).to.equal(’84’) })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const digit4Element = await driver.findElement(By.css(‘.digit-4’))
const digit2Element = await driver.findElement(By.css(‘.digit-2’))
const operatorMultiply = await driver.findElement(By.css(‘.operator-multiply’))
const operatorEquals = await driver.findElement(By.css(‘.operator-equals’))
await digit4Element.click()
await digit2Element.click()
await operatorMultiply.click()
await digit2Element.click()
await operatorEquals.click()
await retry(async() = > {
    const displayElement = await driver.findElement(By.css(‘.display’))
    const displayText = await displayElement.getText()
    expect(displayText).to.equal(’84’)
})

代码 2 – 4 行,定义数字和操作;6 – 10 行模拟点击。实际上想落成的是 “42
* 2 = ”。最后拿到正确的结果——“84”。

小结

总结,Enzyme首要归纳多少个测试:
3个是浅渲染的shallow,那么些生成虚DOM对象,所以渲染最快,然则它并无法测试子组件的相关代码。
另三个是DOM渲染mount,它会转移完整的DOM节点,所以可以测试子组件。但是要重视二个用jsdom模拟的浏览器环境。
最后贰个是HTML文本渲染render,它会将react组件渲染为html文本,然后在里边通过Cheerio自动生成一个Cheerio对象。

渲染方法 是否可以测试子组件 是否可以模拟交互 性能(测试500次)
shallow 116ms
mount 421ms
render 984ms

文中介绍了简单的用法,具体的API文档见:
Shallow
Rendering
Full DOM
Rendering
Static Rendered
Markup

运维测试

一度介绍完了端到端测试和单元测试,将来用 npm test 来运转具有测试:

澳门葡京 12

一次性全体经过!(那是自然的了,不然怎么写文章。)

想说点有关利用 await 的有个别话

您在恐怕互联网上任啥地点方看看有的例证,它们并从未行使
async/await,或许是采纳了
promise。实际上那样的代码是一起的。那么为何也能 work
的很行吗?坦白地说,小编也不领悟,看起来像是在 webdriver 中稍微 trick
的拍卖。正如
selenium文档中协商,在
Node 帮助 async/await 以前,那是三个暂且的缓解方案。

Selenium
文档是 Java
语言。它还不完全,然则包蕴的音讯也充足了,你做五回测试就能操纵这一个技能。

总结

正文中首要性介绍了什么样:

  • 介绍了端到端测试中装置浏览器的代码;
  • 介绍了怎么样利用 webdriver API 来调用浏览器,以及哪些拿到 DOM 中的
    element;
  • 介绍了应用 async/await,因为兼具 webdriver API 都以异步的;
  • 介绍了为啥端到端测试中要运用 retry。

    1 赞 收藏
    评论

澳门葡京 13

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website