Jest 自动化部署测试

11/1/2020 jest

# Jest 简介

随着前端的发展,项目变的越来越复杂,这时候就引入前端工程化的概念,有人认为前端工程化就是高质量的代码设计,高质量的代码实践,就是前端工程化。其实工程化还有一个很重要的环节,就是自动化代码测试。

随着前端的发展,前端设计的领域已经越来越多,也越来越复杂。这就对我们前端工程化能力,提出了更高的要求。 好的前端工程化一般包括三个大的方面:

  • 前端自动化测试(前提条件)
  • 高质量的代码设计
  • 高质量的代码实现

虽然一些公司,到现在还是没有前端自动化测试,甚至BOSS会说前端自动化测试会拉低工作效率,认为用处不大。这是完全错误的想法,你可以看到Github上任何大型的前端项目都有自动化测试代码。

  • Ant Design : React UI组件库
  • Vue.js : 国内最流行的构建用户界面的渐进式JavaScript框架。
  • React.js : 世界最流行的JavaScript MVC框架。

# Jest前端测试框架优点介绍 (opens new window)

  • 比较新:喜新厌旧是人的天性,出来后你总想动手尝试一下,这个就和家花没有野花香是一个道理。作为一个程序员,你更要有拥抱全新知识的态度。绝不能固步自封,顽固不化。
  • 基础很好:曾是框架的基础就是性能好、功能多、简单易用,Jest在这三个方面你可以完全放心。绝对是一把好手,干就完事了。
  • 速度快: 单独模块测试功能,比如说有两个模块A和B,以前都测试过了,这时候你只改动A模块,在次测试,模块B不会再跑一次,而是直接测试A模块。这就好比你去‘大宝剑’,你所有技师都试过了一次,下次你再来,直接就找最好的就行了。不用再测试一遍。(安心、放心)
  • API简单 :等你基础知识学完后,你就会发现API非常简单,数量也少。
  • 隔离性好:Jest里会有很多的测试文件等待我们使用,Jest的执行环境都是隔离,这样就避免不同的测试文件执行的时候互相影响而造成出错。
  • IDE整合:Jest直接可以和很多编辑器(VSCode)进行融合,让测试变的更加简单。
  • 多项目并行:比如我们写了Node.js的后台项目,用React写了一个前台项目,Jest是支持他们并行运行,让我们的效率更加提高了。
  • 快出覆盖率:(测试代码覆盖率) 对于一个项目的测试都要出覆盖率的,Jest就可以快速出这样的覆盖率统计结果,非常好用。

# Jest 基本使用

npm install jest@24.8.0 -D ## 安装jest

编写index.js

在项目根目录,新建两个文件,一个文件是index.js(被测试文件),另一个是index.test.js(测试文件)文件。

index.js文件比如是我们写的一些业务逻辑方法,我们就那他当一个例子,最后要测试的就是这个文件。这里我们模仿一次去按摩的经历。

function baojian1(money){
    return money>=200? '至尊享受':'基本按摩'
}

function baojian2(money){
    return money>=1000? '双人服务':'单人服务'
}
module.exports = {
    baojian1,baojian2  
}

编写index.test.js

const dabaojian = require('./dabaojian.js')
const { baojian1 , baojian2 }  = dabaojian
// 测试baojian1方法, toBe为预期值, 当预期值未达到时, 将视为测试失败
test('保健1 300元',()=>{
    expect(baojian1(300)).toBe('至尊享受')
})
test('保健2  2000元',()=>{
    expect(baojian2(2000)).toBe('双人服务')
})

修改package.json

{
  "name": "jesttest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": { // 修改scriptstest为jest命令
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^24.8.0"
  }
}

# 单元测试与集成测试的区别

  • 单元测试:英文是(unit testing) 单,是指对软件中的最小可测试单元进行检查和验证。前端所说的单元测试就是对一个模块进行测试。也就是说前端测试的时候,你测试的东西一定是一个模块。
  • 集成测试:也叫组装测试或者联合测试。在单元测试的基础上,将所有模块按照涉及要求组装成为子系统或系统,进行集成测试。

随着前端的发展,现在无论我们些React还是写Vue,其实代码已经全部都模块化了,所以使用Jest测试不需要额外加入任何的操作了。

# Jest 初始化配置

jest --init ### 执行初始化配置

#↓↓↓↓↓↓↓↓↓#

The following questions will help Jest to create a suitable configuration for your project ## 以下问题将帮助Jest为您的项目创建合适的配置
? Would you like to use Jest when running "test" script
in "package.json"? » (Y/n) ## 运行测试脚本是否使用package.json中?

#↓↓↓↓↓↓↓↓↓#

? Choose the test environment that will be used for testing » - Use arrow-keys. Return to submit. ## 选择测试环境
>   node
    jsdom (browser-like)

#↓↓↓↓↓↓↓↓↓#

? Do you want Jest to add coverage reports? » (y/N) ## 您是否希望Jest添加覆盖率报告(y)

#↓↓↓↓↓↓↓↓↓#

? Which provider should be used to instrument code for coverage? » - Use arrow-keys. Return to submit. ## 使用哪个提供程序检测覆盖率代码
>   v8
    babel

#↓↓↓↓↓↓↓↓↓#

? Automatically clear mock calls and instances between every test? » (y/N) ## 是否自动清除每个测试之间的模拟调用和实例?(y)

## 自动化测试
jest --watchAll
## 测试覆盖率
jest --coverage

# Jest.config.js配置项

// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
  clearMocks: true, // 是否清除测试模拟调用和实例
  coverageDirectory: "coverage", // 生成覆盖率目录文件名称
  testEnvironment: "node", // 默认值jsdom,
};

# Jest 配置支持ES6 模块化

安装babel模块

cnpm i @babel/core@7.4.5 @babel/preset-env@7.4.5 --dev ## 安装

配置babel模块(.babelrc)

{
  "presets": [
    [
      "@babel/preset-env", {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

运行jest命令:jest

# Jest 匹配器

# 内容匹配

test('测试严格相等',()=>{
  const a = {number:'007'}   
  expect(a).toBe({number:'007'}) // -> 不通过, toBe为完全匹配才算通过
}) 
test('测试内容相等',()=>{
  const a = {number:'007'}   
  expect(a).toEqual({number:'007'}) // -> 通过
})

# 空 / 不存在匹配

test('测试null匹配',()=>{
  const a = null   
  expect(a).toBeNull() // -> 通过
}) 
test('测试undefined匹配',()=>{
  const a = undefined   
  expect(a).toBeUndefined() // -> 通过
})
test('测试不为undefined匹配',()=>{
  const a = 'jspang'  
  expect(a).toBeDefined() // -> 通过
}) 

# 布尔值匹配

test('测试为true匹配',()=>{
  const a = 0
  expect(a).toBeTruthy() // 不通过
}) 
test('测试为false匹配',()=>{
  const a = 0
  expect(a).toBeTruthy() // 通过
}) 

# 数值匹配

test('大于指定值的数匹配', () => {
  expect(10).toBeGreaterThan(9) // ->通过
})
test('小于指定值的数匹配', () => {
  expect(10).toBeLessThan(11) // ->通过
})
test('大于等于指定值的数匹配', () => {
  expect(10).toBeGreaterThanOrEqual(10) // ->通过
})
test('小于等于指定值的数匹配', () => {
  expect(10).toBeLessThanOrEqual(10) // ->通过
})
test('匹配浮点数结果(忽略精度存在问题)', () => {
  const one = 0.1
  const tow = 0.2
  expect(one + tow).toBeCloseTo(0.3) // ->通过
})

# 字符串匹配

test('匹配字符串是否存在指定字符串', () => {
  const str = "谢大脚,刘英,小红"
  expect(str).toMatch('谢大脚')
})

# 数组 / Set匹配

test('匹配数组/Set中某个元素', () => {
  const arr = ["谢大脚", "刘英", "小红"]
  const set = new Set(arr)
  expect(arr).toContain('谢大脚') // -> 通过
  expect(set).toContain('谢大脚') // -> 通过
})

# 错误匹配

const throwNewErrorFunc = () => { throw new Error('this is Error') }
test('匹配该函数是否抛出错误',() => {
  expect(throwNewErrorFunc).toThrow() // -> 通过
  // 匹配该异常字符串是否符合
  expect(throwNewErrorFunc).toThrow('this is Error') // -> 通过
  // 匹配不存在异常(not)
  expect(throwNewErrorFunc).not.toThrow() // -> 不通过
})

# 异步回调测试

在工作中我们很多方法都包含异步操作,所以测试异步代码成了工作中必不可少的一部分。

import axios from 'axios'
export const fetchData = fn => {
  axios.get('http://a.jspang.com/jestTest.json')
    .then(response => fn(response.data))
}
export const pmsFetchData = () => (
  axios.get('http://a.jspang.com/jestTest.json')
    .then(response => response.data)
)
export const fetchThreeData = () => (
  axios.get('http://a.jspang.com/jestTest.json')
    .then(response => response.data)
)

# promise 异步测试

test('异步方法测试', done => {
  // promise需返回, 且done要放在请求完毕后
  return pmsFetchData().then(data => {
    expect(data).toEqual({
      success: true
    })
    // done代表所有操作已完成
    done()
  })
})

# async 异步测试

test('async异步方法测试', async done => {
  // promise需返回, 且done要放在请求完毕后
  const data = await pmsFetchData();
  expect(data).toEqual({
    success: true
  });
  // done代表所有操作已完成
  done();
})

# 404 错误测试

test('异步404错误测试', done => {
  expect.assertions(1) // 断言, 必须执行一次expect(不然则不通过)
  // promise需返回, 且done要放在请求完毕后
  return fetchThreeData().catch(err => {
    expect(err.toString().indexOf('404') > -1).toBe(true)
    // done代表所有操作已完成
    done()
  })
})

# Jest 钩子回调与分组测试

export default class {
  gongzhu(number) {
    this.user = number == 1 ? '大脚' : '刘英'
  }
  anjiao() {
    this.fuwu = this.user + '走进房间为你足疗'
  }
  anmo() {
    this.fuwu = this.user + '走进房间为你按摩'
  }
}

# 四个钩子回调

// beforeAll: 测试案例执行之前
beforeAll(()=>console.log('beforeAll: 吃完饭后,走进了红浪漫区域'))
// afterAll: 测试案例执行之后
afterAll(()=>console.log('afterAll: 有钱人的生活就是这么枯燥且乏味'))
// beforeEach: 每个测试用例执行前
beforeEach(()=>console.log('beforeEach: 给了300元后'))
// afterEach: 每个测试用例之后
afterEach(()=>console.log('afterEach: 完成后, 我心满意足的坐在沙发上'))
test('测试 大脚足疗 方法', ()=>{
  baojian.gongzhu(1)
  baojian.anjiao()
  expect(baojian.fuwu).toEqual('大脚走进房间为你足疗')
})
test('测试 刘英按摩 方法', ()=>{
  baojian.gongzhu(2)
  baojian.anmo()
  expect(baojian.fuwu).toEqual('刘英走进房间为你按摩')
})

# 分组测试

import NewBaoJian from './newBaoJian'
const baojian = new NewBaoJian()
describe('大脚相关服务', () => {
  test('测试 大脚足疗 方法', () => {
    baojian.gongzhu(1)
    baojian.anjiao()
    expect(baojian.fuwu).toEqual('大脚走进房间为你足疗')
  })
  test('测试 大脚泰式保健 方法', () => {
    baojian.gongzhu(1)
    baojian.taishi()
    expect(baojian.fuwu).toEqual('大脚走进房间为你_泰式保健')
  })
})
describe('刘英相关服务', () => {
  test('测试 刘英按摩 方法', () => {
    baojian.gongzhu(2)
    baojian.anmo()
    expect(baojian.fuwu).toEqual('刘英走进房间为你按摩')
  })
  test('测试 刘英宫廷御疗 方法', () => {
    baojian.gongzhu(2)
    baojian.gongting()
    expect(baojian.fuwu).toEqual('刘英走进房间为你_宫廷御疗')
  })
})

# 钩子函数作用域

钩子函数在父级分组可作用域子集,类似继承。钩子函数同级分组作用域互不干扰,各起作用。先执行外部的钩子函数,在执行内部的钩子函数