博客 / 詳情

返回

前端測試(上)

前端測試(上)

在前端項目中,前端測試並沒有被重視,原因有很多,比如 學習/研發成本高團隊不夠重視,或者項目不合適等,在這裏我們不去追究是什麼原因導致這種現象,但是有一點我很確定,造成這種原因,還有一個更重要的原因,就是 “意識不到位”,即使有很多同學瞭解過單元測試,但是也不知道如何應用到 “項目” 中,針對這種現象,我們從一個簡單卻很常見的小項目,來打開測試工程化冰山一角

在刷題的過程中,我們經常會使用一個項目用於練習寫筆試題,比如排序,查找之類的算法題目

石器時代

新建一個工程,目錄如下

├── index.js
├── index.html
└── src
    └── search.js
  • index.html - 基於瀏覽器運行,至少早期的筆試題目,基於瀏覽器即可;此例,引入search.jsindex.js即可
  • index.js - 用於寫測試代碼,比如 console.log(binarySearch([1],1)===0)
  • search.js - 用於寫查找相關的算法代碼,比如順序查找、插值查找等,在這裏我只寫二分法查找順序查找函數
/**
 * 二分法
 * @param {Array} arr 
 */
function binarySearch(arr, expected) {
  let start = 0
  let end = arr.length - 1
  while (start <= end) {
    let mid = parseInt(start + (end - start) / 2)
    let value = arr[mid]
    if (value === expected) {
      return mid
    } else if (value > expected) {
      end = mid
    } else {
      start = mid
    }
  }
  return -1
}
/**
 * 順序查找
 * @param {*} arr 
 * @param {*} expected 
 */
function sequentialSearch(arr, expected) {
  let i = 0
  while (i > arr.length) {
    let value = arr[i]
    if (value === expected) {
      return i
    }
    i++
  }
  return -1
}

OK,大功告成,把頁面拖到瀏覽器中直接運行,連服務器都省了~~!
當我準備為我這個完美的項目鼓掌的時候,眼角瞟到我的座右銘,成為一個專業的大前端,此時此刻 專業這個詞格外刺眼,作為新世紀好青年,我怎麼可能讓別人質疑我的專業,於是我要繼續裝(入)逼(坑)

青銅時代

  1. 使用npm創建工程,並且對模塊進行管理
  2. 使用git對項目倉庫以及迭代進行管理
  3. 載體nodejs環境代替瀏覽器環境,這樣可以測試文件讀取等IO操作
.
├── node_modules
└── test
    └── test.js
└── src
    └── search.js
├── package.json
├── .gitignore
├── index.js    

在 package.json 配置

{
    ....
    "scripts":{
        "test":"node test/test.js"
    }
}

對應 js的模塊 要改成commonjs規範
search.js 調整

function binarySearch(){
    //todo
}
function sequentialSearch(){
    //todo
}
module.exports = {
  binarySearch,
  sequentialSearch
}

index.js 調整

const { binarySearch,sequentialSearch } = require('./src/search')
module.exports = {
  binarySearch,
  sequentialSearch
}

test.js 調整,為了讓提示更加明顯點,我們嘗試讓描述更加豐富點

const { binarySearch,sequentialSearch } = require('../index')
console.log(‘二分查找: [1]的1在數組0位置上’,binarySearch([1],1)===0)
console.log(‘二分查找:[1,2,3]的1在數組0位置上’,binarySearch([1,2,3],1)===0)
console.log(‘二分查找:[1,2,3]的2在數組1位置上’,binarySearch([1,2,3],2)===0)

console.log(‘順序查找:[1]的1在數組0位置上’,sequentialSearch([1],1)===0)
console.log(‘順序查找:[1,2,3]的1在數組0位置上’,sequentialSearch([1,2,3],1)===0)
console.log(‘順序查找:[1,2,3]的2在數組1位置上’,sequentialSearch([1,2,3],2)===0)

一頓操作猛如虎之後,感覺完美的一筆~~
我迫不及待,運行 npm run test

二分查找:[1]的1在數組0位置上 true
二分查找:[1,2,3]的1在數組0位置上 true
二分查找:[1,2,3]的2在數組1位置上 false
順序查找:[1]的1在數組0位置上 false
順序查找:[1,2,3]的1在數組0位置上 false
順序查找:[1,2,3]的2在數組1位置上 false

我們發現 有幾點不足

  1. 當測試用例增加時,測試代碼變的難以管理,所有測試輸出揉在一起,沒有分組的管理
  2. 不管成功或者失敗,沒有高亮顯示,也沒有顏色上的區分
  3. 即使錯誤,也沒有把錯誤詳細打印出來
  4. 沒有相關輸出報表

黃金時代

為了解決 青銅時代 遺留下不少體驗問題,我們不得不封裝一些方法,強化console的輸出,文檔輸出,可視化等輸出,然而我們所做的一切強化,都是新概念 測試框架的雛形,不過在正式介紹 測試框架前,我們先了解下 斷言

“我×,測試框架?斷言?這尼瑪又是什麼?”

斷言是單元測試中用來保證最小單元是否正常的檢測方法,用於判斷邏輯執行是否達到開發者預期的表達式,斷言在運行的過程中,若斷言不為真,程序會中止運行

“常用的斷言庫有哪些?”
  • assert - nodejs的內置核心模塊,node環境可以直接使用
  • shouldjs - 基於assert模塊進行封裝擴展
  • expectjs - 基本是 shouldjs 的縮水版
  • chai - 目前比較流行的斷言庫,支持 TDD(assert),BDD(expect、should)兩種風格

我們先簡單學習 assert, 作為Nodejs內置核心模塊,無需引用,最為 斷言 入門庫最為合適

## assert

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

一旦 assert.equal()不滿足期望,將會拋出AssertionError異常,整個程序將會停止運行

常用的檢測方法
  • ok(actual) - 判斷結果是否為真
  • strictEqual(actual,expected,[,message]) - 判斷實際值和期望值是否嚴格相等
  • deepStrictEqual(actual, expected[, message]) -判斷實際值和期望值是否深度嚴格相等
  • doesNotReject(asyncFn, error) - 判斷代碼塊是否返回reslove
  • rejects(block, error)- 判斷結果返回reject
  • throws(block, error)- 判斷結果是否拋出異常
  • ifError()- 判斷實際值是否為一個假值(null,undefined,0,'',false);如果為真值,就會拋出異常
  • fail([message]) - 直接報錯
“感覺腦殼疼,能不能通俗點?”
先來一個例子,壓壓驚,我們把青銅時代的代碼優化下
 console.log(‘順序查找:[1]的1在數組0位置上’,sequentialSearch([1],1)===0)
 //為了通用性,我們把sequentialSearch([1],1)===0 提煉出來
 function equal(actual,expected,message){
    return actual===expected?message:`${actual}!==${expected}`
 }
  console.log(‘順序查找:[1]的1在數組0位置上’,equal(sequentialSearch([1],1),0,'成功'))

通俗的説 就是 equal這個方法就是斷言

“我迷迷糊糊的貌似明白了一點,那我運行一下嚐嚐鮮吧”

test/index.js

const assert = chai.assert
const { binarySearch } = require('../index')

assert.equal(binarySearch([1], 1), 0)//成功
assert.equal(binarySearch([1], 1), 1)//失敗
assert.equal(binarySearch([1,2], 2), 1)//成功
assert.equal(binarySearch([1,2], 1), 0)//失敗

運行 node test/index.js

//失敗輸出
AssertionError: expected 0 to equal 1
    at Object.<anonymous> (F:\learn\test\index.js:19:8)
“呃....我覺得這體驗,也青銅時代差不多”

我們可以看到,在第二個測試用例執行時,發現代碼執行失敗後,直接退出程序,同時提示你 期望值實際運行值,以及對於錯誤代碼相關提示等等。錯誤提示方面比封裝equire方法強大不少;但是,依舊不能讓我願意使用它。

  1. 成功時,沒有任何提示
  2. 雖然有錯誤提示,但是運行到第一個錯誤的時候,就程序退出;開發者無法看到 自己的測試用例 錯誤多少個
  3. 依舊沒有高亮,可視化方面依舊蒼白

沒錯,斷言拿到非常重要錯誤信息;但是他沒有解決體驗問題;如果説 斷言是裏子,那測試框架 就是面子

“測試框架是什麼?”

測試框架 通俗的説就是專門 服務於代碼塊測試解決方案,他主要有以下功能

  • 管理測試用例
  • 生成測試報告
“常用的測試框架有哪些?”
  • [jasmine]() -自帶斷言(assert),mock 功能
  • [mocha]() -框架不帶斷言和mock功能,需要結合其他工具

通俗的説,測試框架 就是 管理/執行斷言,他和斷言一起使用將會更加強大

Mocha

mocha 是一款強大的測試框架,能夠運行在nodejs和瀏覽器中,能夠高效的管理測試用例,支持多種測試報告格式

  • 支持多種斷言庫:chai/shouldjs/expectjs/assert
  • 支持兩種測試風格:TDD/BDD

    • TDD:基於測試用例 進行測試
    • BDD:基於產品本身功能 進行測試
常用方法
  1. describe(string,callback) -主要用於對測試用例的分組,層級描述。TDD使用suite
  2. it(string [,callback])-測試用例,callback 包含一個或者多個斷言;當callback不存在時,表示這個測試用例需要寫,但目前還未寫入,狀態使用pending表示
  3. hook-用於協助describe中測試用例的準備·安裝·卸載和回收等工作,Hook一般用於describe內,但也可以describe外,作為頂級Hook

    • before/after([ string ,]callback) - 分別在進入或者退出describe時觸發執行
    • beforeEach/afterEach([ string ,]callback) - 分別在describe中每個測試用例執行前和執行後觸發執行

Full example
test/index.js


describe('hooks', function() {
  before(function() {
    console.log('before')
  });

  after(function() {
    console.log('after')
  });

  beforeEach(function() {
    console.log('beforeEach')
  });

  afterEach(function() {
    console.log('afterEach')
  });
  it('Test1',()=>{
    console.log('test1')
  })
  it('Test2',()=>{
    console.log('test2')
  })
  // test cases
});

運行 npm run test

{
    "script":{
        " test":"mocha"
    }
}

hooks
before
beforeEach
test1

√ Test1

afterEach
beforeEach
test2

1) Test2

afterEach
after

1 passing (15ms)
1 failing

1) hooks

   Test2:

  AssertionError: expected 0 to equal 1
  + expected - actual

  -0
  +1

  at Context.it (test\index.js:93:12)
我們可以看到 基於mocha後的斷言,他的提示體驗大大的提升
- 成功後,有相關提示
- 遇到失敗時,依舊可以執行下去,展示所有失敗用例信息
- 統計測試用例成功數和失敗數
4. **異步處理** 
      - done
  it('should save without error', (done)=> {
    var user = new User('Luna');
    user.save((err)=> {
      if (err) done(err);
      else done();
    });
    //user.save(done);
  });

  ```
  - promise
  ```ts
  it('respond with matching records', ()=> {
    return db.find({type: 'User'}).should.eventually.have.length(3);
  });
  ```
  - async/await
  ```ts
  it('responds with matching records', async function() {
    const users = await db.find({type: 'User'});
    users.should.have.length(3);
  });
  ```
    1. Only-屏蔽其他測試單元/測試用例,只執行標識為Only的測試單元/用例。一般用於 當你的單元測試越寫越多時,只想測試新寫的單元測試是否正確,這個屬性就可以幫你在執行時,幫你過濾掉其他測試,加快執行速度

      describe.only('something', function() {
        // 只會跑包在裏面的測試
      })

      或者

      it.only('do do', () => {
        // 只會跑這一個測試
      })
    1. skip-表示執行時,跳過標識的測試單元/測試用例,可以作用於describeit

      it.skip('should return -1 unless present', function() {
        // 代碼不存被執行
      });
      
      it('should return the index when present', function() {
        // 代碼會執行
      });

      可以this.skip()在測試用例執行的時候,根據運行時過濾當前測試案例

      
      describe('outer', function() {
        before(function() {
          this.skip();
        });
      
        after(function() {
          // will be executed
        });
      
        describe('inner', function() {
          before(function() {
            // will be skipped
          });
      
          after(function() {
            // will be skipped
          });
        });
      });
    2. 其他
    • timeout - 超時
    user avatar
    0 位用戶收藏了這個故事!

    發佈 評論

    Some HTML is okay.