FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

前端单元测试那些事儿 #85

Open FrankKai opened 6 years ago

FrankKai commented 6 years ago
FrankKai commented 6 years ago

什么是Snapshot Testing?

Snapshot test是一个可以让你确保自己的UI出现和预期不符的修改时非常有用的工具。

mobile app 渲染一个UI组件,是一个典型的snapshot测试用例,需要生成一个截屏,然后与存储在测试旁边的图片进行比较。如果2张图片不一致,将会导致匹配失败:改变和预期不符,或者是截图需要升级到最新版本的UI组件。

Jest中的Snapshot Testing

Vue和React有不同的解决方案。

FrankKai commented 6 years ago

Common Tips

测什么?

对于UI组件来说,我们不建议将覆盖率到每一行,因为这样需要考虑到组件内部的实现细节,这样的单元测试是非常脆弱的。

正确的做法是,我们推荐在组件的公共接口上写断言,然后将它的内部当作一个黑盒(黑盒指的是只知道输入输出关系,但是不知道内部结构)。一个单独的测试用例,可以将一些输入做断言(用户交互或者是prop的改变),这些输入提供给组件,之后便会产生预期的输出(渲染结果或者是发射的普通的事件)。

例如,对于Counter组件来说,每次按钮被按,展示的只都会加1,测试用例可以模仿点击,然后断言渲染输出增加1.测试用例不care Counter组件内部如何增加的1,它只care输入和输出。

这个方法的好处在于,只要你的组件公共的接口是保持不变的,无论组件内部如何改变,你的测试用例都可以一直用。

Matt 0‘Connell的这个演讲中有很多好的ideas。https://www.youtube.com/watch?v=OIpfWTThrK8 image

FrankKai commented 6 years ago

shallow rendering

在单元测试时,我们通常将被测试的组件作为一个单独的单元,避免直接对它的子组件断言。

另外,如果一个组件包括了许多子组件,整个渲染树将会变得非常庞大。重复地渲染所有的子组件,将会降低我们的测试速度。

Vue Test Utils允许你只挂载一个不渲染它的子组件的组件(通过剔除子组件),这需要使用shallowMount方法。

import { shallowMount } from '@vue/test-utils'

const wrapper = shallowMount(Component)
wrapper.vm
FrankKai commented 6 years ago

Asserting Emitted Events

每个挂载的wrapper自动记录所有的由Vue实例发射的事件。你可以使用wrapper.emitted()方法查询已经记录的事件名。

wrapper.vm.$emit('foo')
wrapper.vm.$emit('foo', 123)

/*
wrapper.emitted()的返回对象是下面这样的:
{
    foo: [[], [123]]
}
*/

你可以基于这些数据写断言:

// 断言事件是否被发射
expect(wrapper.emitted().foo).toBeTruthy()

//断言事件发射数量
expect(wrapper.emitted().foo.length).toBe(2)

//断言事件的载荷
expect(wrapper.emitted().foo[1]).toEqual([123])

可以调用wrapper.emittedByOrder()方法,返回一个按照emit顺序排列的事件数组。

FrankKai commented 6 years ago

Manipulating Component State

可以在wrapper上直接调用setData和setProps,从而修改组件的代码:

wrapper.setData({ count: 10 })
wrapper.setProps({ foo: 'bar' })
FrankKai commented 6 years ago

Mocking Props

可以使用Vue内部的propsData选项,将props传递到组件:

import { mount } from '@vue/test-utils'

mount(Component, {
    propsData: {
        aProp: 'some value'
    }
})

你可以wrapper.setProps({})升级已经挂载的组件的props。 mount 方法还有许多可选的方法。

FrankKai commented 6 years ago

应用全局的插件和混入

有些组件可能依赖一个全局插件或混入 (mixin) 的功能注入,比如 vuex 和 vue-router。

如果你在为一个特定的应用撰写组件,你可以在你的测试入口处一次性设置相同的全局插件和混入。但是有些情况下,比如测试一个可能会跨越不同应用共享的普通的组件套件的时候,最好还是在一个更加隔离的设置中测试你的组件,不对全局的 Vue 构造函数注入任何东西。我们可以使用 createLocalVue 方法来存档它们:

import { createLocalVue } from '@vue/test-utils'

// 创建一个扩展的 `Vue` 构造函数
const localVue = createLocalVue()

// 正常安装插件
localVue.use(MyPlugin)

// 在挂载选项中传入 `localVue`
mount(Component, {
  localVue
})

注意有些插件会为全局的 Vue 构造函数添加只读属性,比如 Vue Router。这使得我们无法在一个 localVue 构造函数上二次安装该插件,或伪造这些只读属性。

FrankKai commented 6 years ago

仿造注入

另一个注入 prop 的策略就是简单的仿造它们。你可以使用 mocks 选项:

import { mount } from '@vue/test-utils'

const $route = {
  path: '/',
  hash: '',
  params: { id: '123' },
  query: { q: 'hello' }
}

mount(Component, {
  mocks: {
    // 在挂载组件之前
    // 添加仿造的 `$route` 对象到 Vue 实例中
    $route
  }
})
FrankKai commented 6 years ago

处理路由

因为路由需要在应用的全局结构中进行定义,且引入了很多组件,所以最好集成到 end-to-end 测试。对于依赖 vue-router 功能的独立的组件来说,你可以使用上面提到的技术仿造它们。

探测样式

当你的测试运行在 jsdom 中时,可以只探测到内联样式。