頂級測試框架Jest指南:跑通一個完美的程序,就是教出一羣像樣的學生

facebook三大項目:yarn jest metro,有橫掃宇宙之勢。 而jest項目的宗旨爲:減小測試一個項目所花費的時間成本和認知成本。 ——其實,它在讓你當一個好老師。

jest文檔很是簡略、難以閱讀, 所以纔有了這篇文章。 jest是vue、react和vue-cli技術棧的重要一環,也是當前最值得掌握的測試框架,對此你須要達到很熟悉的程度。
本文github地址: https://github.com/wanthering...vue

教育和測試,是相通的。node

你能夠野路子自學,但掌握系統化、體系化的知識,終歸離不開一個好老師。react

測試能夠不寫,但當你面對大型複雜的項目之時,沒有測試框架步履維艱。ios

如今,你能夠跟我一步一步學會jest,你將懂得:爲何jest是優雅、簡潔而符合人性的,而且終將成爲測試界的惟一的、最佳的解決方案。git

第一課:jest的初步使用

想象一下,當你走上講臺,五十多個孩子靜靜地看着你。紅撲撲的臉蛋,大大的眼睛。es6

image

接下來,你將課本上知識教給他們了一遍,github

函數運行過程,就至關於你的教學過程。web

請同窗們跟我一塊兒敲下代碼:vue-cli

(請在項目下建立一個文件:lesson1.js)npm

lesson1.js

/**
 * 加法,便是將多個數值逐個累加
 */
exports.sum = (...args) => {
  let res = 0
  for (let i of args) {
    res += i
  }
  return res
}

/**
 * 乘法,便是將b個重複的數值a,進行累加
 */
exports.times = (a, b) => {
  let resArr = (new Array(b)).fill(a)
  return exports.sum(...resArr)
}
以上文件涉及es6和nodejs模塊化的基礎知識,初看可能並很差理解,但相信我,你敲完如下的測試代碼,就會理解它的含義——就像教會一個學生同樣。

就像你js工程師面臨的永恆難題同樣——真的能跑通嗎?

當老師的你,心裏也無比惶恐,第一次教這些,他們真的能懂嗎?

這時須要用上jest進行測試,使用npm安裝:

npm i jest -S

一個合格的老師,會作這幾個步驟:

  1. 提問: 清晰地註明問題.
  2. 叫人回答: 給予一個函數體,運行測試。
  3. 期待答案: expect()
  4. 校驗答案: toBe()、toEqual()

建立一個lesson1.test.js

lesson1.test.js

const {sum, times} = require('./lesson1')

test('同窗,請問這個累加的結果是什麼?',()=>{
  expect(sum(2+2+2+2+2+2)).toBe(12)
})

test('同窗,請問這個乘法的結果是什麼?',()=>{
  expect(times(2,6)).toBe(12)
})

test('那麼,兩個數值的結果是否相等呢?',()=>{
  expect(times(2,6)).toEqual(sum(2+2+2+2+2+2))
})

運行測試方法1:

這時須要運行jest命令,你能夠全局安裝jest:

npm i jest -g

而後

jest lesson1.test.js

運行測試方法2:

也能夠在package json的test字段下寫入

...
  "scripts": {
    "test": "jest"
  },
  ...

而後命令行輸入

npm test

運行測試方法3:

若是你使用的webstorm等IDE的話,文檔中直接顯示綠色小鍵頭,點擊就是了。

測試結果:

接下來,會看到一路pass,證實lesson1.js文件無誤。

咱們讀書這麼多年,深知好老師和壞老師的區別:

上課照本宣科,就像直接寫下滿紙js代碼同樣,這是賴皮狗老師的專長。

但一個優秀的老師,他會:

  • 步步爲營: 分解成不少個子測試,把知詞點逐個擊破。
  • 版書清晰: 標明須要測試什麼,讓知識易學易記。
  • 課堂互動: 回調函數中運行測試,調動孩子們的課堂積極性。
  • 題型豐富: 經過匹配器,即toBe、toEqual等校驗不一樣格式數據。

若是你不是很熟悉jest框架提供的各種匹配器,這裏有一份小抄送給你:jest-cheat-sheet

你看,jest所作的,沒有一絲多餘步驟,也沒少一個必要步驟,這正是咱們這些年遇到的好老師的共有特徵,也正是jest測試的極致之處。

第二課:異步測試獲取數據

jest在異步過程當中也很是方便!

咱們先使用json-server創建服務器,再使用axios獲取數據。

這兩個npm包使用很是普遍,熟悉的axios封裝的能夠直接拷貝server.jsrequest.js代碼。

下載:

npm i json-server axios -S

建立server.js文件

server.js

const jsonServer = require('json-server')

const defaultData = () => ({
  'lesson': [
    { 'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang' },
    { 'id': 2, 'title': 'how to multiply', 'teacher': 'Mr Liu' },
    { 'id': 3, 'title': 'how to subtract', 'teacher': 'Ms Han' }
  ],
  'homework': [
    { 'id': 1, 'works': ['add','multiply'], 'student': 'Jim Green' },
    { 'id': 2, 'title': ['add','subtract'], 'student': 'lily' },
    { 'id': 3, 'title': ['add','subtract'], 'student': 'lucy' },
    { 'id': 4, 'title': ['add','subtract','multiply'], 'student': 'Han Mei Mei' },
    { 'id': 5, 'title': ['subtract','multiply'], 'student': 'Li Lei' }
  ],
  'exam': { 'name': 'primary school final exam' }
})

const createJSONServer = (data = defaultData()) =>{
  const server = jsonServer.create()
  const router = jsonServer.router(data)
  const middlewares = jsonServer.defaults()

  server.use(middlewares)
  server.use(router)
  return server
}

createJSONServer().listen(3000)
console.log('3000端口已聯通')

建立request.js文件

request.js

const axios = require( 'axios' )

class HttpRequest {
  constructor(baseUrl = 'http://localhost:3000') {
    this.baseUrl = baseUrl
  }

  /**
   * 返回默認配置
   */
  getInsideConfig() {
    return {
      baseURL: this.baseUrl,
      headers: {}
    }
  }

  /**
   * 響應欄截,返回指定格式信息
   */
  interceptors(instance) {
    instance.interceptors.response.use(res => {
      const {data} = res
      return data
    }, error => {
      return Promise.reject(error)
    })
  }

  /**
   * 處理網絡請求
   */
  request(options) {
    const instance = axios.create()
    options = Object.assign(this.getInsideConfig(), options)
    this.interceptors(instance)
    return instance(options)
  }
}


/**
 * 導出request模塊
 */
exports.requestPromise =  (port,method='get')=>{
  const Http = new HttpRequest('http://localhost:3000')
  return Http.request({
    url: port,
    method
  })
}

exports.requestCallback = (cb,port,method='get')=>{
  const Http = new HttpRequest('http://localhost:3000')
  Http.request({
    url: port,
    method
  }).then(data=>{
    
    // ▽請註釋掉下面一行,再試一試回調可否跑通?
    cb(data)
  })
}

使用node server跑起服務,咱們就能夠開始測試異步回調了。

老師的困擾: 壞學生「異步回調函數」

班上來了一個壞學生,上課從很差好聽課,恰恰人緣又特別好。

這時,你提問讓壞學生回答,他只是安靜地站起來,什麼都不作,而後過了一會對你說:「老師,這道題我回答過了啊!」

同時,全班同窗都點頭「嗯嗯他回答過了」。

這時候,你,該怎麼辦? 有沒有感覺到深深的絕望?

看一看如下的例子。建立lesson2.test.js,並寫入

lesson2.test.js

const {requestPromise, requestCallback} = require('./request')

test('異步callback方式檢測', () => {
  // 下面進行了拋出了兩次斷言,在斷言以前能夠
  function callback (data){
    expect(data).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
  }

  requestCallback(callback,'lesson/1')
})

跑通了,彷佛一切正常。。。

但當咱們回到request.js註釋掉cb(data)時,咱們知道回調函數並不返回數據,測試理應不經過。然而。。

仍是跑通了!

這是由於requestCallback根本就沒有進入函數體,而測試函數,只要不報錯,都算經過。

異步回調,就是這樣一個壞透了的學生,常常裝做回答過了矇混過關。

異步回調的正確測試方法:

做爲老師,弄死壞學生的方法可能你本身想到了:

「上黑板寫,寫完告訴我!」

你能夠提供一個在測試函數內提供一個done,done必需要調用後,才能算作測試經過。

lesson2.test.js

test('異步callback方式檢測', done => {
  // 下面進行了拋出了兩次斷言,在斷言以前能夠
  function callback (data){
    expect(data).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
    done()
  }

  requestCallback(callback,'lesson/1')
})

這樣,就必需要運行了callback才能經過測試了,不然就會超時報錯。

除了以上方式之外,你還能夠檢驗函數體內的斷言跑了幾回

test('異步callback方式檢測,無done', () => {
  // 下面進行了拋出了兩次斷言,在斷言以前能夠
  expect.assertions(1);
  function callback (data){
    expect(data).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
  }

  requestCallback(callback,'lesson/1')
})

expect.assertions(1)表示, 斷言語句expect(xxx).toXXX(xxx)必須跑通一次,將要檢驗多個「壞學生」異步回調的時候,這招猶其有效。

普通學生:異步Promise

有壞學生,天然有好學和普通學生,promise就是一名「普通的學生」

lesson2.test.js

test('異步Promise方式檢測', () => {
  expect.assertions(1);
  return requestPromise('exam').then(data => {
    expect(data).toStrictEqual({'name': 'primary school final exam'})
  })
})

檢驗異步Promise時,必需要用return返回,不然它就像「壞學生」同樣,直接矇混過關溜走了。

你還可使用使用.resolve/.reject形式

lesson2.test.js

test('異步Promise方式被成功resolve', () => {
  expect.assertions(1);
  return expect(requestPromise('exam')).resolves.toStrictEqual({'name': 'primary school final exam'})
});

若是須要檢驗Promise被reject:

test('異步Promise方式被reject', () => {
  expect.assertions(1);
  return expect(requestPromise('exam')).rejects.toMatch('error')
});

三好學生:async/await異步

上課從不遲到、校服永遠一塵不染、做業按時永遠上交、上課認真作筆記、回答問題完美無缺、別人不聽課她還會打!小!報!告!

這就是咱們的async/await,咱們應該多一些這樣的三好學生。

lesson2.test.js

test('異步async/await方式檢測', async () => {
// 下面進行了拋出了兩次斷言,在斷言以前能夠
expect.assertions(2)

const lesson1 = await requestPromise('lesson/1')
const homework3 = await requestPromise('homework/3')

expect(lesson1).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
expect(homework3).toMatchObject({'student': 'lucy'})
})

優雅,溫馨、簡潔、大方,無需多言,async/await值得你擁有。

雖然咱們不少時候仍是在和異步回調、異步Promise打交道...

第三課:Mock函數,袖口五道槓的風紀委員

假如不是簡單的測試同步返回、測試異步返回,而是須要記錄執行過程當中的狀態呢?

const forEach = (arr, fn) => {
  if (!arr.length || !fn) return
  let i = -1
  let len = arr.length
  while (++i < len) {
    let item = arr[i]
    fn(item, i, arr)
  }
}

假如遇到這樣一個循環函數,內部的運行狀態就不太可能經過返回值來知曉了。這時,你須要Mock函數。

Mock函數,隨時記錄函數運行狀態

測試同步返回值、異步返回值,就像是上課,這只是老師的本份。

而學生大部分時間,都是自習、吃飯、宿舍,老師想管到這一部分,就須要派出讓人聞風喪臉的存在——五道槓·無間道·小報告之王·風紀委員。

她平時混跡在普通學生之中,或者說,她就是一名再普通不過的學生。但,當她遇到老師——即在test()測試體內——一點兒陳芝麻爛谷的破事,都會被抖露出來。

lesson3.test.js

//經過jest.fn()建立Mock Function
const mockCallback = jest.fn(x => 42 + x);
//將mockCallback代入forEach運行一次,便可記錄下全部的值
forEach([0, 1], mockCallback);

test('記錄mockCallback函數運行過程',()=>{
  // mockCallback上報函數運行了兩次
  expect(mockCallback.mock.calls.length).toBe(2);

  // mockCallback上報函數第一次運行的輸入爲0
  expect(mockCallback.mock.calls[0][0]).toBe(0);

  // mockCallback上報函數第二次運行的輸入爲1
  expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
  // mockCallback上報函數第一次運行的結果爲42
  expect(mockCallback.mock.results[0].value).toBe(42);
})

結語

提起教師,隨處可見的朋友圈、公衆號、賀卡
短信裏面,滿是雞湯般的口號:「人類靈魂工程師」、「燃燒本身,照亮他人」「無私」「奉獻」「愛心」。

但,教師的本職——最高效率地輔助學生構建的完整知識體系。 專業性的觀點卻被選擇性忽視。

軟件開發也是,人人在談測試驅動,人人都在強調測試的重要性,測試彷佛成了一個形而上學的感性概念。

但時間和人力成本上、框架的專業性、框架的掌握成本的諸多限制,使大多數項目測試至關於無。另外一方面,少數高度測試覆蓋的項目又顯得十分笨拙,測試耗費的精力竟然比coding還長...

幸之,得益於jest測試框架的產生,一個極致簡潔
功能強大、語義清晰的測試終於呈如今咱們面前。讓開發與測試相輔相成,而非時間加倍。

而最新的vue技術棧正在全面採用jest測試框架。

今後,testing之對於coding,如同孤獨的鋼琴曲中,緩緩傳來小提琴的和絃,頓時錦瑟和鳴,心靈震顫無以復加。

image

前面我已經介紹了 AST抽象語法樹,點擊連接地址,下一期,將結合AST帶來測試驅動開發實戰,這,將是讀懂vue-cli三、並掌握vue全方位技術棧的第一步。

vue-cli3技術棧補全預告:

  • [x] AST抽象語法樹: 童年的玩具
  • [x] jest測試框架: 行爲人師,學爲世範
  • jest測試驅動開發實戰: js配置文件的更新
  • yaml配置文件: 至愛之信
  • config文件導入全配置: 萬能鑰匙
  • vue-share-utils:實用小工具
  • Generator: vue-cli3核心引擎,機械之心
  • jest+Generator: 測試驅動vue-cli3引擎
相關文章
相關標籤/搜索