使用jest進行單元測試

之前,寫完一段代碼我也是直接調用或者實例化一下,發現過了就把測試相關部分刪了。今年的不幸與坎坷使我有很長一段時間去思考人生,不想將就了,魯棒健壯的程序,開發和測試應該是分得很開的,因而我選擇jest去作單元測試這件事。javascript

爲何要作單元測試

在開始以前,咱們先思考這樣一個問題,咱們爲何要作單元測試?html

不扯犢子直接說吧,第一點,用數據、用茫茫多的測試用例去告訴使用者,你的程序是多麼魯棒健壯;第二點,把它做爲一種素養去培養吧,當你按照一系列規範去作事,那麼你作出來的東西,我想是有品質在的。java

jest的安裝

在確保你的電腦裝有node環境的狀況下,咱們經過mkdir jest-study && cd jest-study來初始化項目,而後咱們經過npm init -y初始化npm環境。node

執行npm i jest babel-jest @babel/core @babel/preset-env 命令安裝相應的依賴包,由於後面的例子是基於ES Module的語法編寫的,全部須要安裝babel進行語法轉義。固然你也能夠選擇直接用CommonJS的寫法,node自然支持的。git

jest的相關配置

package.json中相關scripts

這裏筆者羅列了經常使用的通用的一些關於jest的腳本,後面測試結果會陸續補充一些測試腳本,以上的腳本都編寫在package.json文件下的scripts腳本下面。github

通用寫法

"test": "jest" : 這個比較傻瓜式,當執行npm run test這條命令是會去對test目錄下的全部文件進行相應的jest測試。npm

"test:help": "jest --help": 顧名思義,若是你不想全局安裝jest,又想看看到底有哪些cli命令的話,就它了。json

"test:debug": "jest --debug": 顧名思義,debug啊。promise

"test:verbose": "jest --verbose": 以層級顯示地方式在控制檯展現測試結果。緩存

"test:noCache": "jest --no-cache": 顧名思義,就是設置有沒有緩存,有緩存的話會快點。

"test:init": "jest --init": 執行這句就是在根目錄建立一個jest.config.js文件,它在建立的時候有不少選擇項給你的。

"test:caculator": "jest ./test/caculator.test.js": 單文件測試。

"test:caculator:watch": "jest ./test/caculator.test.js --watch": 單文件監視測試

"test:watchAll": "jest --watchAll": 監視全部文件改動,測試相應的測試。

大體基礎類的腳本測試就總結到這裏,接下來咱們看下jest.config.js的相關配置。

jest.config.js中相關配置

裏面配置的參數太多了,有些配置了之後就能夠再也不package.json文件下寫相應的腳本,這裏筆者閹割一部分,列舉最多見的幾個。

module.exports = {
  // Automatically clear mock calls and instances between every test
  clearMocks: true,
  // The directory where Jest should output its coverage files
  coverageDirectory: "coverage",
  // The test environment that will be used for testing
  testEnvironment: "node",
}

babel相關配置

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

這裏就是配置了相應的語法轉換預設,若是後期有其餘需求,能夠經過plugins去配置寫補丁轉義器,相關內容這裏就不作介紹了,能夠看下筆者以前寫的關於babel的文章。

測試結果

考慮到把相關信息打在控制檯上,第一,控制檯可能會出現一處的狀況;第二,在查看結果內容多的話可能引發眼睛不適,全部就有了樓下幾種可能。

測試覆蓋率

package.json中的scripts下配置 "test:coverage": "jest --coverage"後,而後執行相應腳本,就會在根目錄輸出一個coverage文件夾,裏面包含了相應的測試腳本。固然控制檯也會輸出的。

html顯示

執行 npm i jest-html-reporter安裝這個模塊包(這裏說起一下,在npm版本大於5.x之後,能夠默認不加--save這種參數),而後在jest.config.js中配置以下:

reporters: [
    "default",
    ["./node_modules/jest-html-reporter", {
      "pageTitle": "Test Report"
    }]
  ],

執行相關的jest測試後,會在根目錄生成一個test-report.html文件,打開後形如:

json顯示

package.json中配置scripts腳本 "test:exportJson": "jest --json --outputFile=./export/reporter.json",而後執行npm run test:exportJson就會輸出相應的json報告文件,控制檯也會以json的形式輸出相應信息。

斷言(expect)

斷言庫的種類有不少,例如、assert、should、expect、chai等等,樓下的例子,筆者均以expect做爲講解。

not

先說個最簡單的expect(received).not.toBe(expected),這句話的意思就是表示否對,表示我斷言、接收值不等於指望值。

toBe(expected)

這個API經常使用於斷言,值類型的指望值,也就是boolean、string、number、這些類型的,用它作引用類型的斷言是不合適也不可取的。

to_be.test.js

describe('#toBe', () => {
  it('to be string', () => {
    expect('hello world').toBe('hello world')
  })

  it('to be number', () => {
    expect(1 + 1).toBe(2)
  })

  it('to be boolean', () => {
    expect(true).toBe(true)
    expect(false).toBe(false)
  })

  it('to be null', () => {
    expect(null).toBe(null)
  })

  it('to be undefined', () => {
    expect(undefined).toBe(undefined)
  })
})

toEqual(expected)

通俗的理解就是等於, 能夠是值類型也能夠是引用類型的相等。

to_equal.test.js

test('#toEqual', () => {
  expect('hello world').toEqual('hello world')
  expect(110).toEqual(110)
  expect(true).toEqual(true)
  expect(false).toEqual(false)
  expect(null).toEqual(null)
  expect(undefined).toEqual(undefined)
  expect([1, 2, 3, 4]).toEqual([1, 2, 3, 4])
  expect({ name: 'ataola' }).toEqual({ name: 'ataola' })
})

toContain(expected) && toContainEqual(expected)

toContain()跟的指望值是值類型的,而toContainEqual() `能夠是值類型也能夠是引用類型,表示包含。

to_contain.test.js

test('#toContain', () => {
  expect([1, 2, 3, 4]).toContain(1)
  expect([[1, 2], [3, 4], [5, 6]]).toContainEqual([1, 2])
})

數值比較

樓下expect後面跟的英語的字面量意思就是其方法的做用,分別是,大於、大於等於、小於、小於等於、類似於(接近於),這裏值得一題的事最後一個toBeCloseTo(),思考一下改爲toBe()能夠嗎?很顯然不行,其算出來的結果是0.30000000000000004,究其緣由是js採用的是雙精度浮點表示。

number_compare.test.js

test('number compare', () => {
  expect(3).toBeGreaterThan(2)
  expect(3).toBeGreaterThanOrEqual(2.5)
  expect(3).toBeLessThan(4)
  expect(3).toBeLessThanOrEqual(3.5)
  expect(0.1 + 0.2).toBeCloseTo(0.3) // <0.05 passed
})

toMatch(expected)

顧名思義,字符串匹配,它支持字符串和正則,/^(\w+)\1+$/匹配的是一個字符串能夠由其字串經過n次組合而成的字串(leetcode一道題目),全部其匹配到的是tao

string_match.test.js

test('string match', () => {
  expect('ataola').toMatch('ataola')
  expect('ataola').not.toMatch('aloata')
  expect('taotao'.match(/^(\w+)\1+$/)[0]).toMatch('tao')
})

內置的一些基本類型值

null、undefined、真假值比較特殊,全部這裏單獨有個方法表示它們。

truthiness.test.js

// toBeNull、 toBeUndefined 、 toBeDefined 、 toBeTruthy、 toBeFalsy

test('truthiness', () => {
  expect(null).toBeNull()
  expect(undefined).toBeUndefined()
  expect('i am defined').toBeDefined()
  expect(true).toBeTruthy()
  expect(false).toBeFalsy()
})

ToThrow(expected)

這裏是處理相關異常的, 後面能夠什麼都不根,也能夠跟個Error,或者相應的Error輸出信息

exceptions.test.js

function gaoError() {
  throw new Error('二營長開炮,開炮,開炮。。。')
}

test('#ToThrow', () => {
  expect(gaoError).toThrow()
  expect(gaoError).toThrow(Error)
  expect(gaoError).toThrow('二營長開炮,開炮,開炮。。。')
})

好了,到這裏比較基礎和通用的API就介紹到這裏。接下來,咱們經過本身編寫相關代碼去鞏固下樓上的知識,這裏筆者提供兩個demo,一個是關於異步獲取數據的斷言、一個是實現一個計算器類的斷言。

異步

咱們經過request-promise這個庫去請求https://v1.hitokoto.cn去獲取相應的json數據,而後進行斷言。

hitokoto.js

import rp from 'request-promise'

const getHitokoto = async () => {
  const res = await rp('https://v1.hitokoto.cn')
  return res
}

export default getHitokoto

hitokoto.test.js

import getHitokoto from '../src/hitokoto'

test('hitokoto', async () => {
  const data = await getHitokoto()
  expect(data).not.toBeNull()
})

這裏就意思下,讀者能夠把data裏面的數據解構出來,進行相應的斷言。

計算器

這裏模擬了筆者手機上的計算器,實現了加減乘除清零計算等功能。

caculator.js

class CaCulator {

  constructor() {
    this.result = 0
  }

  add(...args) {
    let { result } = this
    result += args.reduce((pre, cur) => pre + cur)
    this.result = result
    return this
  }

  reduce(...args) {
    let { result } = this
    result -= args.reduce((pre, cur) => pre + cur)
    this.result = result
    return this
  }

  multiply(...args) {
    let { result } = this
    if (result) {
      for (const val of args) {
        result *= val
      }
    } 
    this.result = result
    return this
  }

  divide(...args)  {
    let { result } = this
    const has_zero = args.some(item => item === 0)
    if (has_zero) {
      result = '數學體育老師教的嗎?'
    } else {
      for (const val of args) {
        result /= val
      }
    }
    this.result = result
    return this
  }

  clear() {
    this.result = 0
    return this
  }

  exec() {
    const { result } = this
    if (typeof result === 'string') {
      this.result = 0
    }
    return result
  }

  init(n) {
    this.result = typeof n === 'number' ? n : 0
    return this
  }

}

export default CaCulator

caculator.test.js

import Caculator from '../src/caculator'
const caculator = new Caculator()

describe('test Caculator', () => {
  test('#add', () => {
    expect(caculator.add(1).exec()).toBe(1)
    expect(caculator.clear().add(1, 2, 3).exec()).toBe(6)
    caculator.clear()
  })
  
  test('#reduce', () => {
    expect(caculator.reduce(1).exec()).toBe(-1)
    expect(caculator.clear().reduce(1, 2, 3).exec()).toBe(-6)
    caculator.clear()
  })
  
  test('#multiply', () => {
    expect(caculator.multiply(1).exec()).toBe(0)
    expect(caculator.init(1).multiply(2, 3, 4, 5).exec()).toBe(120)
    caculator.clear()
  })
  
  test('#divied', () => {
    expect(caculator.divide(0).exec()).toBe('數學體育老師教的嗎?')
    expect(caculator.divide(1, 2).exec()).toBe(0)
    expect(caculator.init(100).divide(2, 2).exec()).toBe(25)
  })
})

這裏筆者只是羅列了平常開發中經常使用的斷言API,具體的仍是要參見官方文檔這樣的一手資料,但願能起到拋磚引玉的效果。

參考文獻

https://jestjs.io/

https://github.com/Hargne/jest-html-reporter#readme

https://jestjs.io/docs/en/configuration

https://jestjs.io/docs/en/expect

https://jestjs.io/docs/en/using-matchers

知識共享許可協議
本做品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

相關文章
相關標籤/搜索