第三方框架小程序单测(Vue Test Utils + JEST)

一、背景:
项目采用第三方框架uni-app开发微信小程序,vue语法 + wxApi

二、单测方案:
Vue Test Utils + JEST

前期调研:

  1. 微信官方推荐单测工具 【miniprogram-simulate】,官方提供的案例demo都针对原生小程序开发方案,尝试了测试第三方打包后的小程序代码,load方法无法渲染
  2. uni-app提供了测试方案【@dcloudio/uni-automator】,属于自动化测试。测试需要编译代码,api提供的示例方案偏重于渲染组件,判断当前渲染的组件属性是否与预期一致,暂无法自定义测试时组件入参。
  3. 选用vue提供的单测方案【Vue Test Utils】,可搭配用 Jest,Mocha,Karma 等单测运行器,但是由于我们项目中用了微信API,不引入过多第三方插件的前提下,需要自己做一部分兼容。

综上:目前来说,方案一二官方提供的API不是很符合我们的需求,方案三相对来说更合适一些。

三、单测实施

1、测什么
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。
这个问题有很多博客写过答案(大部分来自与https://geosoft.no/unittesting.html 这个的翻译)。对于前端来说,我们的测试最小单元,也就是方法,组件。

2、FIRST原则

FIRST原则
F-FAST(快速原则) 单元测试应该是可以快速运行的,在各种测试方法中,单元测试的运行速度是最快的,通常应该在几分钟内运行完毕
I-Independent(独立原则) 单元测试应该是可以独立运行的,单元测试用例互相无强依赖,无对外部资源的强依赖
R-Repeatable(可重复原则) 单元测试应该可以稳定重复的运行,并且每次运行的结果都是相同的
S-Self Validating(自我验证原则) 单元测试应该是用例自动进行验证的,不能依赖人工验证
T-Timely(及时原则) 单元测试必须及时的进行编写,更新和维护,以保证用例可以随着业务代码的变化动态的保障质量

3、结果检验-指标含义

coverage
%stmts是语句覆盖率(statement coverage): 是不是每个语句都执行了?
%Branch分支覆盖率(branch coverage): 是不是每个if代码块都执行了?
%Funcs函数覆盖率(function coverage): 是不是每个函数都调用了?
%Lines行覆盖率(line coverage): 是不是每一行都执行了?

3、实际操作:

  • Vue Test Utils使用:
    vue test utils官网。

根据官网提示傻瓜式安装就好了
简单使用:

import { mount } from '@vue/test-utils'
// 你要测试的组件
import Counter from './counter'

// 渲染配置, 比如,你期望的data 初始值,props, store, slot,等
const config = {}
// 现在挂载组件,你便得到了这个包裹器, 就是你渲染的那个组件啦
const wrapper = mount(Counter, config)

// 你可以通过 `wrapper.vm` 访问实际的 Vue 实例
const vm = wrapper.vm //(比如可以调用实例的方法)

说明: wrapper是包裹器,vue test utils提供了对它操作的很多方法,比如.html(),.text (), .findComponent()等,用来获取内容,获取某个元素等,方便做断言判断,现在有很多即将废弃的方法,尽量避免使用。
config: 渲染组件的配置,可任意搭配

 const store = new Vuex.Store({
 	state: {},
  	actions: {
     	aaa: jest.fn()
   	}
 })
 config = {
   propsData: {
     ddd: '1234'
   },
   filters: { bbb: jest.fn() },
   store,
   stubs,
   localVue,
   listeners: {
     ccc: jest.fn()
   }
 }

常用方法: 比如这些:

import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
const config = {}
wrapper = shallowMount(com, config)
wrapper.attributes()
wrapper.classes()
wrapper.findComponent('div')
wrapper.exists()
wrapper.setData({ aaa: 123 })
wrapper.setProps({ bbb: 123 })
...

tips: 我在项目中用到最多的是findComponent(class选择器)用户获取当前测试组件中的某个元素和子组件。这里有个小问题,findAllComponents 方法是用来查全部的,感觉类似queryselector 和 queryselectorAll。但是,findAllComponents查找的时候,如果查找的是多个子组件,那么返回的wrapperArray是有length的,调用正常,同api一致,如果查找div元素,返回值永远都是类似这种{selector: ‘.class’},无法获取length!!!!

  • jest配置:
    jest官网

jest.config.js: 有很多配置,我目前只配了关键用到的

module.exports = {
  testEnvironment: 'jsdom', // 运行环境
  moduleFileExtensions: [
    'js',
    'json',
    'vue'
  ],
  setupFiles: [
    '/src/unitTest/setup.js' // 执行单测前执行的文件
  ],
  transform: {
    '.*\\.(vue)$': 'vue-jest',
    '^.+\\.js?$': 'babel-jest'
  },
  moduleNameMapper: {
    '^@/(.*)$': '/src/$1' // 解析@目录
  },
  testTimeout: 15000,
  reporters: [
    'default'
  ],
  watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
  rootDir: __dirname,
  testMatch: ['/src/**/unitTest/components/*test.[jt]s?(x)'] // 测试文件目录
}

setup.js:
执行单测前会执行的文件,比如你定义的一些值。由于我的项目其实用了wxAPI嘛,vue是不识别的。对于单测来说,只需要测试某个方法是否执行,组件渲染是否与预期一直,存在边界值的时候是否兼容等,我们会mock很多方法。对于一些wxAPI的方法,项目中直接执行时是会报错的,jsdom环境不识别。所以,这些我们就可以在setup.js中去处理。
比如,我们在mounted里用到了wx.createSelectorQuery,单测时组件执行渲染就会报错,这个时候就可以在这里处理啦。当然啦,在当前组件的单测文件中处理也是可以的。

global.EVENTS = [
  'touchstart',
  'touchmove',
  'touchcancel',
  'touchend',
  'click', // tap=>click
  'longpress',
  'longtap',
  'transitionend',
  'animationstart',
  'animationiteration',
  'animationend',
  'touchforcechange',
  'getphonenumber'
]

global.COMPONENTS = []
const configFun = {
  getStorageSync () {},
  reportAnalytics () {},
  showToast () {}
}

configFun.createSelectorQuery = () => {
  return {
    in: () => {
      return {
        select: (selector) => {
          return {
            boundingClientRect (fn) {
              return {
                exec: function () {
                  fn({ width: 375 })
                }
              }
            }
          }
        }
      }
    }
  }
}

global.uni = global.wx = configFun

mock数据,mock函数:
对于组件中的函数,业务逻辑的话,完全可以用jest.fn()来代替。当我们单测只需要测试组件事件触发是否正确的调用函数,并不关心函数的执行结果时,可以用mock的函数代替原函数

比如:
(1)我们期望按钮点击触发onClick函数,可以这样写

 const spyFn = jest.spyOn(wrapper.vm, 'onClick')
 wrapper.findComponent('.btn').trigger('click')
 expect(spyFn).toHaveBeenCalled()

(2)我们期望按钮点击触发$emit函数,可以这样写

const submitBtnClick = jest.fn()
  beforeAll(() => {
    wrapper = mount(com, {
      listeners: {
        submitBtnClick: submitBtnClick // 组件中点击时会$emit('submitBtnClick')
      }
    })
  })
 wrapper.findComponent('.r-item__head').trigger('click')
 expect(submitBtnClick).toHaveBeenCalled()

你可能感兴趣的:(微信小程序,JavaScript,前端,小程序,vue.js,前端)