開始測試React Native App(下篇)

前言:node


開始測試React Native App(上篇)中編寫了redux-upload-queue針對ReducerAction Creator的單元測試,測試代碼能夠在這裏查閱。這篇文章基於開始測試React Native App(上篇)繼續完成集成測試以及E2E測試。react

集成測試

Action Creator的測試中,引入了redux-mock-store庫,按官方的話來講,這個庫只是用來測試Redux async action creatorsmiddleware,它不是用來測試reducer相關的邏輯,換句話來講它不會更新Redux Store,因此若是你想把reduceraction結合在一塊兒測試建議使用redux-actions-assertions。 筆者在剛學測試時沒有認真看文檔這段話,致使寫出了以下代碼:android

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實現告終合reduceraction的測試,能更改Redux Store,在性能上確定是不優的,每次獲取State都要遍歷全部派發的actionreducer。因此仍是建議使用redux-actions-assertions,在該篇文章中採用的是不優的解決方案。ios

在解決了以上測試的技術點後,就能夠開始寫組合reduceraction在一塊兒的集成測試了:git

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.mockResponsesmock了屢次網絡請求的返回結果來模擬上傳的結果,因此模擬出了第一次上傳時第三個文件(fileThree)上傳失敗,失敗後再次派發上傳動做UploadActions.upload返回成功,判斷整個流程走完後store.getActions()store.getState()是否符合預期。github

這個測試用例涉及到了派發action,使用reducer處理action,以及更改Store的狀態,因此它是一個集成測試,也是單元測試的組合測試。web

示例代碼json

其實集成測試更加的符合初學者對測試的直觀想法,好比當我說我要測試上傳組件的redux邏輯是否有問題時,天然而然就會想到要派發一系列action,再看reducer是否能正常的處理這些actionStore結果是否符合預期。在redux-upload-queue這個組件中,不但實現了Redux處理上傳隊列的整套邏輯,還使用HOC的方式,讓任意組件能夠快速的集成上傳隊列功能,例如:redux

...
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來模擬組件裝載,而後使用ShallowWrapperprops()來獲取被裝載的Foo組件的全部屬性,調用屬性的pushUploadItemstartUpload方法來觸發上傳操做,預期會觸發與以前測試上傳隊列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元素,而後執行TapActions來觸發各類手勢輸入等(模擬用戶),最後經過isVisibleMatcher來判斷指望值),保證App的核心流程正確。它依賴Native端的灰盒測試框架:EarlGrey for iOSEspresso for Android,使用基於JSON的反射機制,讓JavaScript直接調用Native測試框架的方法,在JavaScript端提供了一系列易於使用API,徹底的抽象了Native引擎下發生的複雜調用邏輯,所以它寫出來的測試可讀性高。 測試腳本與被測試的App的通訊原理:

image.png

依賴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環境測試時運行的代碼。

注意: 一、同步狀態難以解救時要去看下一本身的代碼是否是使用setTimeout等不當操做形成了濫用資源,內存泄漏。 二、react-native-repackager在0.55.*以後的版本由於這個PR而再也不須要,但依然是經過定義E2E的flavor來使用特定的*.e2e.js自定義擴展名。

Mock

以前提到過,E2E測試受環境因素影響大,例如網絡狀態,模擬器中沒有圖片庫,沒有聯繫人等,想象一下若是咱們可以在運行E2E測試時達到如下需求:

  • 使用本地的Mock HTTP Server來代替生產環境中真實的服務器訪問(這裏推薦個人美女同事寫的兩篇文章:先後端分離——數據mockjson-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庫:

這些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:

歡迎關注個人簡書主頁:www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^

相關文章
相關標籤/搜索