开始测试React Native App(下篇)

栏目: IOS · Android · 发布时间: 6年前

内容简介:在在变相的使用

前言:

开始测试React Native App(上篇) 中编写了 redux-upload-queue 针对 ReducerAction Creator 的单元测试,测试代码可以在 这里 查阅。这篇文章基于 开始测试React Native App(上篇) 继续完成集成测试以及E2E测试。

集成测试

Action Creator 的测试中,引入了 redux-mock-store 库,按官方的话来说,这个库只是用来测试 Redux async action creatorsmiddleware ,它不是用来测试 reducer 相关的逻辑,换句话来说它不会更新 Redux Store ,所以如果你想把 reduceraction 结合在一起测试建议使用 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 实现了结合 reduceraction 的测试,能更改 Redux Store ,在性能上肯定是不优的,每次获取 State 都要遍历所有派发的 actionreducer 。所以还是建议使用 redux-actions-assertions ,在该篇文章中采用的是不优的解决方案。

在解决了以上测试的技术点后,就可以开始写组合 reduceraction 在一起的集成测试了:

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 是否能正常的处理这些 actionStore 结果是否符合预期。在 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 来模拟组件装载,然后使用 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的通信原理:

开始测试React 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-repackagerBetter 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:

  • 用来做UI测试:enzyme,以及输出Json的配套库 enzyme-to-json
  • 用来React Native E2E测试: detox

欢迎关注我的简书主页: www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^


以上所述就是小编给大家介绍的《开始测试React Native App(下篇)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

第二曲线:跨越“S型曲线”的二次增长

第二曲线:跨越“S型曲线”的二次增长

[英]查尔斯·汉迪(Charles Handy) / 苗青 / 机械工业出版社 / 2017-6 / 49.00

S型曲线是每个组织和企业在预测未来时一定会参考的工具,一切事物的发展都逃不开S型曲线(“第一曲线”)。 然而,从公司组织、企业治理、市场的变化,到个人职业发展、社会人际关系以及未来的教育与社会价值,多维度地探讨这个世界需要重新以不同的角度来思考问题,不能够总是停留在“第一曲线”的世界。 如果组织和企业能在第一曲线到达巅峰之前,找到带领企业二次腾飞的“第二曲线”,并且第二曲线必须在第一曲......一起来看看 《第二曲线:跨越“S型曲线”的二次增长》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具