# 第 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 可以發掘所有模塊文件的依賴圖表,從這個圖表中不難歸結出分片所需要的信息。

## 彈射和配置 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 頁面進行搜索引擎優化,那么沒有必要使用“同構”來增加開發難度;如果希望應用的性能能更進一步,而且服務器運算資源充足,那么可以嘗試。
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs