前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈前端测试

浅谈前端测试

作者头像
顾翔
发布2019-12-12 12:00:44
1.6K0
发布2019-12-12 12:00:44
举报

顾翔老师开发的bugreport2script开源了,希望大家多提建议。文件在https://github.com/xianggu625/bug2testscript,

主文件是:zentao.py 。bugreport是禅道,script是python3+selenium 3,按照规则在禅道上书写的bugreport可由zentao.py程序生成py测试脚本。

来源:http://www.51testing.com

前端测试或许被好多人误解,也许大家更加倾向于编写面向后端的测试,逻辑性强,测试方便等

  聊到这导致了好多前端从来不写测试(测试全靠手点~~~)

  其实没必要达到测试驱动开发的程度,只要写完代码可以补测试,并且补出高效的测试,前端或许真的不需要手点

  大前端时代不谈环境不成方圆,本文从下面几个环境一一分析下如何敏捷测试

  node 环境

  vue 环境

  nuxt 服务端渲染环境

  react 环境

  next 服务端渲染环境

  angular 环境

  理解测试前需要补充下单元测试(unit)和端到端测试(e2e)的概念,这里不赘述

node 环境

  推荐测试框架 jest

  jest 是 FB 的杰作之一,方便各种场景的 js 代码测试,这里选择 jest 是因为确实方便

  使用方法及配置信息可以去官方文档

  配置的注意事项

{  testEnvironment: 'node' // 如不声明默认浏览器环境  }

  针对 node 只聊一下单元测试,e2e 测试比较少见

  当决定写一个 npm 模块时,代码完成后必不可少的就是单元测试,单元测试需要注意的问题比较琐碎

 mock

  当引入三方库时,不得不 mock 数据,因为单元测试更多讲求的是局部测试,不要受外界三方引入包的影响

  例如:

const { readFileSync } = require('fs')  const getFile = () => {  try {  const text = readFileSync('text.txt', 'utf8')  } catch (err) {  throw new Error(err)  }  console.log(text)  }  module.exports = getFile

  这时我们并不需要关心 text.txt 是否真的存在,也不需要关系 text 的内容具体是什么,我们的关注点应该在于读取文件错误时能否及时抛出异常,以及 console.log() 是否如预期执行

  对应到测试

const getFile = require('./getFile')  describe('readFile', () => {  const mocks = {  fs: {  readFileSync: jest.fn()  },  other: {  text: 'Test text'  }  }  beforeAll(() => {  jest.mock('fs', () => mocks.fs)  })  test('read file success run console.log', () => {  mocks.fs.readFileSync.mockImplementation(() => this.mocks.other.text)  getFile()  expect(console.log).toBeCalled()  })  })

  上面代码简单的实现了一个读取文件是否成功的测试,先别急着纠错,这段测试本身是错的,下面慢慢分析

  我们在最开始创建了一个 mocks 对象,用来模拟数据,由于 readFileSync 方法可能存在多种返回结果(成功或报错),所以暂时用 jest.fn() 模拟

  other 里面则是放一些固定的测试数据(不会随着测试过程而改变)

  beforeAll 钩子里面执行我们的 mock,把 require 进来的 fs 模块拦截调,也是本测试用例中的关键步骤

  在第一个 test 里面我们改写 mocks.fs.readFileSync 的返回形式,这里使用的 mockImplementation 是直接模拟了一个执行函数,当然也可以模拟返回值,具体可以到 jest 官网

  expect 用来断言我们的 console.log 方法执行了

  解释了这么多测试新手们应该也都看的明白了,下面聊一下错在哪,怎么改进

  1.mockImplementation 最好替换为 mockReturnValueOnce,注意这里出现了 Once 结尾,也就是仅模拟一次返回值,mockImplementation 最好使用在复杂场景,所谓的复杂就是我们手动实现一个 readFileSync 方法使得测试达到我们预期的目的,在这个简单的场景里面我们只需要模拟返回值就好

  2.expect(console.log) 这里会报错,因为 jest 断言的内容只能是 mock function 或 spy,这里 console 是全局对象 global 上的方法,我们没有 require 将其引入,所以 jest.mock 显然处理上有些吃力,这时候 spy 就派上用场了,beforeAll 钩子里直接执行 jest.spyOn(global.console, 'log'),接下来我们就能监听到 console.log 的执行了 expect(global.console.log)

  3.断言的目的是测试 console.log 的执行,这是不严谨的测试,我们需要使用 toBeCalledWith 来代替 toBeCalled,不仅要测试执行了,而且要测试参数正确,简单修改为 expect(global.console.log).toBeCalledWith(this.mocks.other.text)

  下面补一下 read file 失败的测试

test('read file fail throw error', () => {  mocks.fs.readFileSync.mockImplementationOnce(() => { throw new Error('readFile error') })  expect(getFile()).toThrow()  expect(global.console.log).not.toBeCalled()  })

  读取文件失败的测试就好理解的多,注意的就是对一个 jest.fn() 多次进行修改会导致测试用例之间的相互影响,这里尽量使用 Once 结尾方法,复杂场景可以如下

beforeEach(() => {  mocks.fs.readFileSync.mockReset()  })

  每次执行 test 前先清除 mock,避免多个测试用例之间复杂化 mock 导致错误

  小结:单元测试中的 mock 是个测试思路,我们无需关心外部文件和依赖是什么,只要能模拟出正确的情况程序是否按规则执行,错误的情况程序是否有异常处理,逻辑是否正确等。这样就能排除外界干扰,使得我们测试的当前一小部分是可靠的,稳定的即可。

引用外部文件

  单拿出一个小结说下 require 的问题,node 9 之前不支持 es6 的 import,这里也不详细说明了。

  require 本身并不复杂,但是如果搞不清楚执行时机,那么测试将无法进行,来一个例子

const env = process.env.NODE_ENV  module.export = () => env

  测试如下

const getEnv = require('./getEnv')  describe('env', () => {  test('env will be dev', () => {  process.env.NODE_ENV = 'dev'  expect(getEnv()).toBe('dev')  })  test('env will be pord', () => {  process.env.NODE_ENV = 'pord'  expect(getEnv()).toBe('pord')  })  })

  十分简单的测试,抛开了 mock 的流程,这里会报测试未通过,原因是 require 同时 env 已经被赋值为 undefined,我们再试着改变 NODE_ENV 环境变量时,程序不会再次执行,当然了,处理起来也十分简单

let getEnv  test('env will be dev', () => {  process.env.NODE_ENV = 'dev'  getEnv = require('./getEnv')  expect(getEnv()).toBe('dev')  })  test('env will be pord', () => {  process.env.NODE_ENV = 'pord'  getEnv = require('./getEnv')  expect(getEnv()).toBe('pord')  })

  顺带说了一下,希望大家不要在这种低级错误上浪费时间

  其实引用外部文件还有些场景会对测试带来困惑,比如动态路径,场景如下

const packageFile = `${process.cwd()}/package.json`  const package = require(packageFile)

  读取当前路径下的 package.json,当测试真正跑到这段代码时会到当前目录下找 package.json,这里尽量 mock 掉 package.json 为我们自己的模拟数据,但是 jest 不支持动态路径的 mock,试着这样写 jest.mock(${process.cwd()}/package.json, () => mockFile) 会报错,所以尽量使用可以 mock 的方案,保证单元测试可以顺利进行,修改如下

const path = require('path')  const filePath = path.join(process.cwd(), 'package.json')

  这样就可以 mock,path 了,和上面 mock 章节,大致思想都差不多

 覆盖率

  单元测试覆盖率不达标等于白测,测试过程尽量覆盖所有判断条件,而不是全部通过了就不管了,在进一阶说,100% 的测试覆盖率并不证明一定覆盖到位了,因为顺带执行的代码也会算进覆盖率,例如

module.export = (list) => list.map(({ id }) => id)

  我们先不考虑这个 list 类型是不是数组,只是简单的例子,避免过度设计带来复杂化,我们测试可以这样

const getId = require('./getId')  const mocks = {  list: [{  id: 1,  name: 'vue'  }, {  id: 2,  name: 'react'  }]  }  test('return id', () => {  expect(getId(mocks.list)).toEqual([1, 2])  })

  直到有一天代码变成了 module.export = (list) => [1, 2]

  这时候测试还能通过,并且覆盖率 100%,的确不会有人蠢到把代码改成这样,只是一个例子,实际上逻辑会比这个复杂的多

  那就聊一聊解决方案

  mock 数据的随机化,每次测试生成随机的 list 进行测试,现有库 mockjs

  强关联测试,证明 map 方法的确执行了,并且参数正确,先 spy spyOn(Array.prototype, 'map') 然后断言

  聊了一圈从覆盖率聊到了测试健壮性的问题,可以思考下写过的测试是否真的满足注释或修改任何一行代码都能引起测试的 pass 报错

  关于 node 就聊这么多,其实下文主要思想都一样,更多的是介绍些简单可行的方案,以及可能会踩坑的地方

星云测试

http://www.teststars.cc

奇林软件

http://www.kylinpet.com

联合通测

http://www.quicktesting.net

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 软件测试培训 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档