💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 第 8 章 单元测试 ## 单元测试的原则 从不同的角度,可以将测试划分为如下不同的种类: - 从人工操作还是写代码来操作的角度,分为手工测试和自动化测试 - 从是否需要考虑系统的内部设计的角度,分为白盒测试和黑盒测试 - 从测试对象的级别,分为单元测试、集成测试和端到端测试 - 从测试验证的系统特性,可分为功能测试、性能测试和压力测试 ...... 单元测试是一种自动化测试,测试代码和被测的对象非常相关,比如测试 React 组件的代码就和测试 jQuery 插件的代码完全不是一回事。 ## React 和 Redux 的单元测试环境 1.React 和 Redux 单元测试框架的选择 最常见的是以下两种: - Mocha:Mocha 没有断言库,往往需要配合 Chai 断言库来使用 - Jest:Jest 自带断言等功能,相当于包含了 Mocha 和 Chai 的功能,不过 Jest 的语法和 Chai 并不一致 create-react-app 创建的应用中自带了 Jest 库,执行`npm run test`就会进入单元测试界面。Jest 会自动在当前目录下寻找满足下列任一条件的 JavaScript 文件作为单元测试代码来执行。 - 文件名以 .test.js 为后缀的代码文件 - 存于 \_\_test\_\_ 目录下的代码文件 一种方式时在项目的根目录上创建一个名为 test 的目录,和存放功能代码的 src 目录并列,在 test 目录下建立和 src 对应子目录结构,每个单元测试文件都以 .test.js 后缀,就能被 Jest 找到。 ## 单元测试代码组织 单元测试代码的最小单位是测试用例(test case),每一个测试用例考验的是被测试对象在某一特定场景下是否有正确的行为。在 Jest 框架下,每个测试用例用一个 it 函数代表,it 函数的第一个参数是一个字符串,代表的就是测试用例的名称,第二个参数是一个函数,包含的就是实际的测试用例过程,一个简单的例子如下: ``` it('should return object when invoked', () => { // 增加断言语句 }) ``` 比较好的测试用例名遵循这样的样式:“(它)在什么样的情况下是什么行为”,应该尽量在 it 函数的第一个参数中使用这样有意义的字符串。 为了测试被测对象在多种情况下的行为,就需要创建多个单元测试用例,因此,接下来的问题就是如何组织多个 it 函数实例,也就是测试套件(test suite)的构建。 一个测试套件由测试用例和其他测试套件构成,于是测试套件和测试用例形成了一个树形的组织结构,当执行某个测试套件的时候,按照从上到下从外到里的顺序执行所有的测试用例。 在 Jest 中用 describe 函数描述测试套件,例子如下: ``` describe('actions', () => { if('should return object when invoked', () => { }) // 可以有更多的 it 函数调用 }) ``` describe 中有如下特殊函数可以帮助重用代码: - beforeAll:在测试套件开始之前执行一次 - afterAll:在结束测试套件中所有测试用例后执行一次 - beforeEach:在每个测试用例执行之前都执行一次 - afterEach:在每个测试用例执行之后都执行一次 假设一个 describe 中包含上面所描述的四个函数,并包含两个 it 测试用例,那么首先执行 beforeAll 函数,随后执行 beforeEach 函数,接下来执行一个 it 函数,接着执行 afterEach 函数,然后又依次执行 beforeEach、it、afterEach 函数,最后执行 afterAll 函数。 ## 辅助工具 **1.Enzyme** `npm install --save-dev enzyme react-addons-test-utils` react-addons-test-utils 是 Facebook 提供的单元测试辅助库,Enzyme 依赖于这个库。Enzyme 支持三种渲染方法,有时我们不需要渲染整个 DOM 树来测试 - shallow:只渲染顶层 React 组件,不渲染子组件,适合只测试 React 组件的渲染行为 - mount:渲染完整的 React 组件包括子组件,借助模拟的浏览器环境完成事件处理功能 - render:渲染完整的 React 组件,但是只产生 HTML,并不进行事件处理 ```js const Filter = () => { <p className="filters"> <Link filter={FilterTypes.ALL}>{FilterTypes.ALL}</Link> <Link filter={FilterTypes.COMPLETED}>{FilterTypes.COMPLETED}</Link> <Link filter={FilterTypes.UNCOMPLETED}>{FilterTypes.UNCOMPLETED}</Link> </p> } ``` 例如,测试上面的 Filter 组件时,如果只专注于 Filter 的功能,只要保证这个渲染结果包含 Filter 组件就够了,没有必要把 Link 组件内容渲染出来,因为那是 Link 组件的单元测试应该做的事情,这时可以用 shallow **2.sinon.js** `npm install --save-dev sinon` sinon.js 可以改变指定对象的行为,甚至改变测试环境的时钟测试 **3.redux-mock-store** `npm install --save-dev redux-mock-store` 模拟 Redux Store ## 单元测试 Redux 各个部分的方法 action 构造函数测试: ``` it('should create an action to add todo', () => { const text = 'first todo' const action = addTodo(text) expect(action.text).toBe(text) expect(action.completed).toBe(false) expect(action.type).toBe(actionTypes.ADD_TODO) }) ``` 1. 预设参数 2. 调用纯函数 3. 用 expect 验证纯函数的返回结果 reducer 测试: reducer 是纯函数,所要做的就是创造 state 和 action 对象,传递给 reducer 函数,验证结果即可 ``` if('should return loading status', () => { const action = actions.fetchWeatherStarted() const newState = reducer({}, action) expect(newState.status).toBe(Status.LOADIN) }) ``` > 以后有写到单元测试再总结吧...... # 第 10 章 动画 在网页中,实现动画无外乎两种方式: - CSS3,利用浏览器对 CSS3 的原生支持实现动画 - 脚本方式,通过间隔一段时间用 JavaScript 来修改页面元素样式来实现动画 CSS3 方式运行效率比脚本方式高,因为浏览器原生支持,省去了 JavaScript 的解释执行负担,有的浏览器(如 Chrome)还可以利用 GPU 加速来进一步增强动画渲染的性能,不过用来实现细粒度的动画较为困难。 脚本方式最大的好处就是更强的灵活度,只不过消耗的计算资源更多,如果处理不当,动画可能会出现卡顿滞后现象。 关于 16ms:每秒渲染 60 帧(也叫 60fps,60 Frame Per Second)会给用户带来足够流畅的视觉体验,一秒钟有 1000 毫秒,1000 / 60 ≈ 16。 ## ReactTransitionGroup `npm install react-transition-group --save` 书中介绍的是 v1 版本,现在都 v2 版本了,原理是否大致相同? 这个库是借助 CSS3 的功能来实现动画,其主要是帮助组件实现装载过程和卸载过程的动画,而对于更新过程,并不是其要解决的问题。 [https://reactcommunity.org/react-transition-group/](https://reactcommunity.org/react-transition-group/) ## React-Motion 动画库 [https://github.com/chenglou/react-motion](https://github.com/chenglou/react-motion) react-motion 采用的是脚本的方式来实现动画 # 第 11 章 多页面应用 书中提到的多页面应用应该就是现在说的“单页面富应用”,即页面跳转不刷新。 代码分片:在大型应用中,因为功能很多,若把所有页面的 JavaScript 打包到一个 bundle.js 中,那么用户首次访问就需要很长的加载时间。所以,当应用变得比较大之后,就应该把 JavaScript 进行分片打包,然后按需加载。 代码分片的原则:根据页面来划分,如果有 N 个页面,那就划分出 N 个分片,不过,多个页面可能使用共同的组件及 React 库部分代码,所以共同的代码需要抽取出来放在一个共享的打包文件中。 webpack 能帮助我们实现分片,其工作方式就是根据代码中的 import 语句和 require 方法确定模块之间的依赖关系,所以 webpack 可以发掘所有模块文件的依赖图表,从这个图表中不难归结出分片所需要的信息。 ![](https://box.kancloud.cn/bca11a4c05169f2292dc909fc9d8fffd_907x537.png) ## 弹射和配置 webpack 我们需要让应用从 create-react-app 制造的“安全舱”里弹射出来,才能直接操作 webpack 的配置文件:`npm run eject` 执行完这个命令之后会发现多了 scripts 和 config 两个目录,有两个 webpack 配置,分别是开发环境`webpack.config.dev.js`和产品环境`webpack.config.prod.js` 书中是 react-router v3.0 版本(先配置 webpack 实现代码分片,然后利用 Route 的 getComponent 属性异步加载 React 组件),现在好像利用新的 API react-loadable 配合 withRouter 可以实现路由懒加载? # 第 12 章 同构 “同构”(Isomorphic)即同一份代码可以在不同环境下运行,理想情况下,一个 React 组件或者说功能组件既能够在浏览器端渲染也可以在服务器端渲染产生 HTML。 服务器端渲染即:对于来自浏览器的 HTTP 请求,服务器通过访问存储器或者访问别的 API 服务之类的方式获得数据,然后根据数据渲染产生 HTML 返回给浏览器,浏览器只要把 HTML 渲染出来,就是用户想要看的结果。 传统上,一个浏览器端渲染的方案,一般包含以下几个部分: - 一个应用框架,包含路由和应用结构功能, Redux 这样遵循单向数据流的框架配合 React-Router 就可以胜任 - 一个模板库,比如 mustache,通过模板库开发者可以定义模板,模板以数据为输入,输出的就是 HTML 字符串,可以插入到网页之中,React 可以替换模板库的功能 - 服务器端的 API 支持,因为应用代码完全部署在页面的 JavaScript 中,获取数据不能像服务端渲染那样有直接访问数据库的选择,只能要求有一个提供数据的 API 服务器,通常就是一个 RESTful API React 可以将网页内容(HTML)、动态行为(JavaScript)、样式(CSS)全部封装在一个组件中,把浏览器端渲染的应用发挥到了极致。 为了量化网页性能,我们定义两个指标: - TTFP(Time To First Paint):指的是从网页 HTTP 请求发出,到用户可以看到第一个有意义的内容渲染出来的时间差 - TTI(Time To Interactive):指的是从网页 HTTP 请求发出,到用户可以对网页内容进行交互的时间 在一个完全靠浏览器端渲染的应用中,当用户在浏览器中打开一个页面的时候,最坏情况下没有任何缓存,需要等待三个 HTTP 请求才能到达 TTFP 的时间点: - 向服务器获取 HTML,这个 HTML 只是一个无内容的空架子,但是皮之不存毛将焉附,这个 HTML 就是皮,在其中运行的 JavaScript 就是毛,所以这个请求是不可省略的 - 获取 JavaScript 文件,大部分情况下,如果这是浏览器第二次访问这个网站,就可以直接读取缓存,不会发出真正的 HTTP 请求 - 访问 API 服务器获取数据,得到的数据将由 JavaScript 加工之后用来填充 DOM 树,如果应用的是 React,那就是通过修改组件的状态或者属性来驱动渲染 > 按照 Progressive Web App 的规格,可以通过 Manifest 和 Service Worker 技术进一步优化,避免第一个获取 HTML 的请求和第三个访问 API 的请求,但是这些技术也并不适用于所有网页应用 而对于服务器端渲染,因为获取 HTTP 请求之后就会返回所有有内容的 HTML,所以在一个 HTTP 的周期之后就会提供给浏览器有意义的内容,所以首次渲染时间 TTFP 会优于完全依赖于浏览器端渲染的页面 > 除了更短的 TTFP,服务器端渲染还有一个好处就是利于搜索引擎优化,虽然某些搜索引擎已经能够索引浏览器端渲染的网页,但是毕竟不是所有搜索引擎都能做到这一点,让搜索引擎能够索引到应用页面的最直接方法就是提供完整 HTML 上面的性能对比只是理论上的分析,实际上,采用服务器端渲染是否能获得更好的 TTFP 有多方面因素。 1.服务器端产生的 HTML 过大是否会影响性能?因为服务器端渲染返回的是完整的 HTML,那么下载这个 HTML 的时间也会增长。 2.服务器端渲染的运算消耗是否是服务器能够承担得起的?浏览器端渲染的方案下,服务器只提供静态资源,压力被分摊到了访问用户的浏览器中;如果使用服务器端渲染,**每个页面请求都要产生 HTML 页面**,这样服务器的压力就会很大。 React 并不是给服务器端渲染设计的,如果应用对 TTFP 要求不高,也不希望对 React 页面进行搜索引擎优化,那么没有必要使用“同构”来增加开发难度;如果希望应用的性能能更进一步,而且服务器运算资源充足,那么可以尝试。