前言:
在開始測試React Native App(上篇)中編寫了redux-upload-queue針對Reducer
和Action Creator
的單元測試,測試程式碼可以在這裡查閱。這篇文章基於開始測試React Native App(上篇)繼續完成整合測試以及E2E測試。
整合測試
在Action Creator
的測試中,引入了redux-mock-store庫,按官方的話來說,這個庫只是用來測試Redux async action creators
和 middleware
,它不是用來測試reducer
相關的邏輯,換句話來說它不會更新Redux Store
,所以如果你想把reducer
和action
結合在一起測試建議使用redux-actions-assertions。
筆者在剛學測試時沒有認真看文件這段話,導致寫出瞭如下程式碼:
const rootReducer = combineReducers({
upload: UploadReducer
})
let initState = {}
export const store = mockStore((actions) => {
let currentState = initState
actions.forEach(action => {
currentState = rootReducer(currentState, action)
});
return currentState
})
複製程式碼
變相的使用redux-mock-store
實現了結合reducer
和action
的測試,能更改Redux Store
,在效能上肯定是不優的,每次獲取State
都要遍歷所有派發的action
和reducer
。所以還是建議使用redux-actions-assertions,在該篇文章中採用的是不優的解決方案。
在解決了以上測試的技術點後,就可以開始寫組合reducer
和action
在一起的整合測試了:
import * as UploadActions from '../UploadActions'
import config, {store} from './UploadConfig'
afterEach(() => {
store.clearActions()
fetch.resetMocks()
})
...
//使用reducer和action模擬多張圖片部分上傳失敗,重新上傳成功的整合測試
it('upload mult fail and reupload action test', () => {
fetch.mockResponses(
[
JSON.stringify({ error: null, id: '123456' })
],
[
JSON.stringify({ error: null, id: '123456' })
],
[
JSON.stringify({ error: new Error('fail') })
],
[
JSON.stringify({ error: null, id: '123456' })
],
)
store.dispatch(UploadActions.registerUpload({upload: 'uploadKey'}))
store.dispatch(UploadActions.pushUploadItem({upload: 'uploadKey', name: 'fileOne', filePath: 'filePathOne'}))
store.dispatch(UploadActions.pushUploadItem({upload: 'uploadKey', name: 'fileTwo', filePath: 'filePathTwo'}))
store.dispatch(UploadActions.pushUploadItem({upload: 'uploadKey', name: 'fileThree', filePath: 'filePathThree'}))
return store.dispatch(UploadActions.upload('uploadKey', config))
.then(() => {
return store.dispatch(UploadActions.upload('uploadKey', config))
})
.then(() => {
expect(store.getActions()).toMatchSnapshot()
expect(store.getState()).toMatchSnapshot()
})
})
複製程式碼
上面的測試程式碼首先派發出注冊上傳佇列的動作(UploadActions.registerUpload
),然後依次派發出在註冊的上傳佇列中新增上傳項的動作(UploadActions.pushUploadItem
),再派發非同步上傳動作(UploadActions.upload
)開始上傳,因為使用fetch.mockResponses
mock了多次網路請求的返回結果來模擬上傳的結果,所以模擬出了第一次上傳時第三個檔案(fileThree
)上傳失敗,失敗後再次派發上傳動作UploadActions.upload
返回成功,判斷整個流程走完後store.getActions()
和store.getState()
是否符合預期。
這個測試用例涉及到了派發action
,使用reducer
處理action
,以及更改Store
的狀態,所以它是一個整合測試,也是單元測試的組合測試。
其實整合測試更加的符合初學者對測試的直觀想法,比如當我說我要測試上傳元件的redux
邏輯是否有問題時,自然而然就會想到要派發一系列action
,再看reducer
是否能正常的處理這些action
,Store
結果是否符合預期。在redux-upload-queue這個元件中,不但實現了Redux
處理上傳佇列的整套邏輯,還使用HOC
的方式,讓任意元件可以快速的整合上傳佇列功能,例如:
...
import {redux_upload} from 'redux-upload-queue'
class Foo extends Component {
componentDidMount() {
this.props.pushUploadItem('fileOnePath', 'fileOne')
this.props.startUpload()
}
render() {return <View/>}
}
...
export default redux_upload({ upload: 'uploadKey', config: config })(Foo)
複製程式碼
上面的示例中對元件Foo
快速的整合了上傳佇列的功能,那麼redux_upload
是否能正確的讓被包裹的元件有上傳功能呢?我們可以寫以下整合測試用例來證明:
import config, {store, Foo} from './UploadConfig'
import uploadComponent from '../UploadComponent'
...
test('uploadComponent new', () => {
fetch.mockResponseOnce(JSON.stringify({ error: null, id: '123456' }))
const Component = uploadComponent({ upload: 'uploadKey', config: config })(Foo)
const componentWrap = shallow(
<Component store={store}/>
)
const fooWrap = componentWrap.shallow()
const fooProps = fooWrap.props()
fooProps.pushUploadItem('fileOnePath', 'fileOne')
return fooProps.startUpload().then(() => {
expect(store.getActions()).toMatchSnapshot()
})
})
複製程式碼
通過shallow
來模擬元件裝載,然後使用ShallowWrapper
的props()
來獲取被裝載的Foo
元件的所有屬性,呼叫屬性的pushUploadItem
和startUpload
方法來觸發上傳操作,預期會觸發與之前測試上傳佇列Redux
邏輯差不多的Actions
,都是先註冊佇列,新增上傳項然後開始上傳等,只是註冊的唯一識別符號、新增上傳項的物件以及數量、上傳的結果不同。
注意<Component store={store}/>
這一行程式碼,store={store}
是用來給Redux connect
提供Store
的,相當於使用react-redux
中的Provider
:<Provider store={store}><Component/></Provider>
。
示例程式碼
E2E測試
E2E測試就是編譯安裝App到模擬器或真機,在App中模擬使用者的行為進行測試,一般用來測試App的主要流程(不是全部流程),因為E2E測試受周邊環境影響較大(網路等因素),因此測試結果不完全可靠。
Detox
Detox
是一個移動App自動化E2E灰盒測試框架,是第一個支援React Native
專案的E2E
測試框架,可以結合Jest
使用,安裝與配置也是很快的。
在Detox
的設計原則中我們可以瞭解到它是一個灰盒測試框架,這種測試框架是從App的內部操控測試(通過testId
等方式查詢到UI元素,然後執行Tap
等Actions
來觸發各種手勢輸入等(模擬使用者),最後通過isVisible
等Matcher
來判斷期望值),保證App的核心流程正確。它依賴Native
端的灰盒測試框架:EarlGrey for iOS
和Espresso for Android
,使用基於JSON的反射機制,讓JavaScript直接呼叫Native
測試框架的方法,在JavaScript端提供了一系列易於使用API,完全的抽象了Native
引擎下發生的複雜呼叫邏輯,因此它寫出來的測試可讀性高。
測試指令碼與被測試的App的通訊原理:
依賴websockets
讓執行在nodejs
的測試指令碼和執行在裝置上的App通訊,實現了真實的雙工通訊,相比與其他類似REST的協議要更快更靈活,執行在nodejs
端的測試讓它能在多個平臺上執行。
測試與App同步
這種E2E測試指令碼執行App流程,讓人最困惑的的就是它是如何將測試與App同步,App複雜的操作(例如訪問伺服器資料或執行動畫)經常需要大量的時間去完成,在這些操作完成之前我們不能繼續執行測試程式碼,否則會使測試失敗(例如正在測試登入流程,在沒登入成功時你就斷言進入首頁,測試就會失敗),那我們如何將測試與App同步?
經常會想到的解決方案是手動執行sleep()
來同步,但是在不同的裝置上,不同的網路狀態下,執行相同的操作所花費的時間不同,手動sleep()
要不會造成不必要的時間浪費,讓測試變慢,要不時間不給充足會讓測試直接失敗。
Detox
的同步方式是:自動同步,這種同步方式就像魔術一樣,你在寫了一行測試程式碼後寫下一行測試程式碼無需關心中間的時間間隔問題(資料是不是還沒有獲取到,轉場動畫是否還沒執行完等),Detox
會等待App穩定之後才會去執行下一行程式碼。例如有一個已經發出的網路請求,那麼直到網路請求完成測試才會執行下一行程式碼。
這種自動同步要百分之百的正確是非常困難的,經常會有一些異常情況,Detox
正在對這些異常情況進行優化,因此大部分情況下都需要考慮同步問題。那如果遇到了同步問題應該怎麼辦呢?這裡給出了具體的解決方案,包括:
- 不能自動同步的原因。
- 可以自動同步的場景。
- 手動切換到非自動同步模式,然後使用
waitFor
做手動同步。 - 使用react-native-repackager重寫e2e下執行的程式碼,就像
*.ios.js
、*.android.js
一樣,可以通過*.e2e.js
來載入在E2E環境測試時執行的程式碼。
注意: 1、同步狀態難以解救時要去看下一自己的程式碼是不是使用
setTimeout
等不當操作造成了濫用資源,記憶體洩漏。 2、react-native-repackager在0.55.*之後的版本因為這個PR而不再需要,但依然是通過定義E2E的flavor
來使用特定的*.e2e.js
自定義副檔名。
Mock
之前提到過,E2E測試受環境因素影響大,例如網路狀態,模擬器中沒有圖片庫,沒有聯絡人等,想象一下如果我們能夠在執行E2E測試時達到以下需求:
- 使用本地的Mock HTTP Server來代替生產環境中真實的伺服器訪問(這裡推薦我的美女同事寫的兩篇文章:前後端分離——資料mock、json-server 接入專案說明用來Mock伺服器資料)。
- 當執行在模擬器時,不去訪問裝置上的聯絡人,而是返回Mock的聯絡人。
在對真實專案E2E時,諸如以上的場景還有許多,因此可以使用build flavouring
來自定義file extensions
,然後編寫Mock。這樣可以大量減少受E2E執行結果受環境因素的影響,具體可以看react-native-repackager和
Better support for custom file extensions (and build flavours)。
Artifacts
最後一個我特別喜歡的功能點,那就是Artifacts了,它可以在測試過程中以多種方式記錄下測試過程,例如錄影、截圖、log等。大家有興趣可以看文件,簡單實用。
總結選擇Detox進行E2E測試的原因:
- 程式碼跨平臺,因為它是在nodejs中執行的。
- 可以在真機、模擬器中執行App。
- 結合Jest使用時,易配置,上手難度小。
- 使用async/await自動同步測試和App的狀態,大部分情況下無需寫
waitFor
。 - 使用Artifacts可以在測試過程中錄製視訊和截圖。
- 提供全面的Actions,例如點選(單點,多點,長按)、滑動(上下左右四個方向以及速度位置的控制)、輸入文字、滾動等。
- 提供Matcher獲取UI元素,可以通過testID、文字內容、nativeViewType來定位UI,即可以查詢到
js
端定義的UI,可以查詢到native
端定義的UI。 - 提供Expect來斷言期望值,可以斷言UI元素是否存在,是否可見。
還有更多好用的API可以在文件中查閱。
可執行官方示例體驗:github.com/wix/detox/t…
完(初)
之所有寫【完(初)】,是因為在這裡測試的初級學習就結束了,其實在學習測試的過程中Mock是一個很重要的概念,幾乎無處不在Mock,想必大家在文章中也看到了我用的Mock庫:
- 在Jest測試中Mock fetch使用的庫:jest-fetch-mock。
- 在Jest測試中Mock Redux Store 的
Action creators
和Middleware
的庫:redux-mock-store。 - RN0.37前Mock React Native的庫:react-native-mock,0.37後RN自帶Mock React Native,所以就不需要這個庫了。
- 在Jest測試中Mock Redux Store:redux-actions-assertions,用來做Redux的整合測試。
- 在E2E測試中Mock網路請求:前後端分離——資料mock、json-server 接入專案說明
這些Mock不限於Jest中的Mock,還有在編寫開發程式碼中的Mock,除了使用這些Mock庫,在實際專案中寫測試時也需要自己寫大量的對第三方庫或者對自己寫的模組的Mock,以及React Native雖然自帶Mock但是還有一些模組(Platform等)沒有被Mock到,也要自己Mock。除了模組的Mock還有Function的Mock,資料的Mock,Global的Mock等等,具體參考Testing React Native Apps。
Test Runner:
- Jest:JavaScript測試框架
Test Utility:
- 用來做UI測試:enzyme,以及輸出Json的配套庫enzyme-to-json。
- 用來React Native E2E測試:detox
歡迎關注我的簡書主頁:www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^