Jest单元测试入门

8,450 阅读4分钟

单元测试主要包含断言、测试框架、测试用例、测试覆盖率、mock、持续集成等几个方面,由于Node的特殊性,它还会加入异步代码测试和私有方法的测试这两个部分。

断言

在程序设计中,断言是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示程序开发者预期的结果——当程序运行到断言的位置时,对应的断言应该为真。若断言不为真,程序会中止运行,并出现错误信息。

断言用于检查程序在运行时是否满足期望。

node自带断言就是assert模块,具体使用请查看官方文档:地址

const assert = require('assert');
assert.equal(Math.max(1, 100), 100);

常用的第三方断言库有chai,官方地址:链接

chai

测试框架

前面提到断言一旦检查失败,将会抛出异常停止整个应用,这对于做大规模断言检查时并不友好。更通用的做法是,记录下抛出的异常并继续执行,最后生成测试报告。这些任务的承担者就是测试框架。

常用的第三方测试框架有mocha,jest,他们之间的区别在于jest内容更加全面,集成了断言,jsdom模拟浏览器DOM环境。

jest

测试用例

每个功能至少要包含一个测试用例,今天介绍jest写测试用例,先安装jest和@types/jest,后面这个是类型声明文件,可以帮助我们在写代码的时候会有语法提示。

npm i jest @types/jest

默认会测试spec和test结尾的js文件,所有写单元测试的文件名必须是sum.spec.js或者sum.test.js这个的格式。

jest

然后需要在package.json里面增加一段脚本

{
  "scripts": {
    "test": "jest"
  }
}

通过运行npm run test最后会生成测试报告

jest

在测试用例中使用it语法表示一个用例,expect表示预期结果,toBe表示三个等号的比较,如果条件成立单元测试即可通过。

匹配器

相当,不相等,包含,等等,匹配的关系

// describe表示分组,分组里面一个个的用例
describe('测试基本方法', () => {
  it('测试sum函数', () => {
    expect(sum(1, 2)).toBe(3)
  })
  it('测试1+1=2', () => {
    expect(1 + 1).toBe(2)
  })
  it ('对象比较', () => {
    expect({name: 1}).toEqual({name: 1})
  })
})

it('测试不相等', () => {
  expect(1+1).not.toBe(3) // 1+1不等3
  expect(3).toBeLessThan(5) // 3<5
})

it('测试包含', () => {
  expect('hello').toContain('h') // 参数是字符串
  expect('hello').toMatch(/h/) // 参数可以是正则
})

测试DOM

it('测试删除DOM', () => {
  document.body.innerHTML = `<div><button></button></div>`

  let button = document.querySelector('button')
  expect(button).not.toBe(null)

  // 自己写的移除的DOM方法
  removeNode(button);

  button = document.querySelector('button')
  expect(button).toBe(null)
})

测试异步

// 回调
export const getDataCallback = fn => {
  setTimeout(() => {
    fn({name: 'callback'})
  }, 1000);
}

// promise
export const getDataPromise = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({name: 'promise'})
    }, 1000);
  })
}

import { getDataCallback, getDataPromise } from '../util'

// 异步回调
it('测试回调函数', (done) => {
  // done参数
  getDataCallback((data) => {
    expect(data).toEqual({ name: 'callback' })
    done() // 标识调用完成
  })
})

it('测试promise', () => {
  // 返回的promise会等待完成
  return getDataPromise().then(data => {
    expect(data).toEqual({ name: 'promise' })
  })
})

it('测试promise', async () => {
  // 使用await语法
  const data = await getDataPromise()
  expect(data).toEqual({ name: 'promise' })
})

mock

如果我们要测试接口,比如使用axios发送api请求

import axios from 'axios'
// 测试接口调用
export const fetchUser = () => {
  return axios.get('/list')
}

我们并不会真实的发送API请求,而是模拟数据返回,这个时候可以在__mocks__下面建axios.js文件,jest就不会再使用node_modules文件中的axios发请求了,而是使用我们自己写的axios模块。

// mock axios
export default {
  get(url) {
    return new Promise((resolve, reject) => {
      // 根据请求路径mock数据
      if (url === '/list') {
        resolve({ name: 'hello' })
      }
    })
  }
}
import { fetchUser } from '../api'

it('测试请求列表', async () => {
  // 把请求的逻辑mock
  const data = await fetchUser()
  expect(data).toEqual({name: 'hello'})
})

测试覆盖率

想要查看测试覆盖率,我们需要先生成配置文件,通过命令

npx jest --init

然后还需要在package.json中添加一段脚本

{
  "scripts": {
    "test": "jest --coverage"
  }
}

通过命令运行npm run test最后会生成测试覆盖率

jest
还会在coverage目录的lcov-report目录里生成HTML文件,可以通过浏览器打开查看。

测试风格

我们将测试用例的不同组织方式称为测试风格,现今流行的单元测试风格主要有TDD(测试驱动开发)和BDD(行为驱动开发)两种。

关注点不同。

  1. TDD关注所有功能是否被正确实现,每一个功能都具备对应的测试用例;一般为先写好测试用例,然后再开发代码,这样就可以让测试用例覆盖所有代码。
  2. BDD关注整体行为是否符合预期,适合自顶向下的设计方式。一般是先开发代码,再写单元测试。这个较为常用。