1. 程式人生 > 遊戲資訊 >重灌機兵2RNEXS完結修正版7一週目高價值物品收集

重灌機兵2RNEXS完結修正版7一週目高價值物品收集

前言

對於現在的前端工程,一個標準完整的專案,通常情況單元測試是非常必要的。但很多時候我們只是完成了專案而忽略了專案測試。我認為其中一個很大的原因是很多人對單元測試認知不夠,因此我寫了這邊文章,一方面期望通過這篇文章讓你對單元測試有一個初步認識。另一個方面希望通過程式碼示例,讓你掌握寫單元測試實踐能力。

前端為什麼需要單元測試?

  1. 必要性:JavaScript 缺少型別檢查,編譯期間無法定位到錯誤,單元測試可以幫助你測試多種異常情況。

  2. 正確性:測試可以驗證程式碼的正確性,在上線前做到心裡有底。

  3. 自動化:通過 console 雖然可以打印出內部資訊,但是這是一次性的事情,下次測試還需要從頭來過,效率不能得到保證。通過編寫測試用例,可以做到一次編寫,多次執行。

  4. 保證重構:網際網路行業產品迭代速度很快,迭代後必然存在程式碼重構的過程,那怎麼才能保證重構後代碼的質量呢?有測試用例做後盾,就可以大膽的進行重構。

現狀

下面是一份抽樣調查片段,抽樣依據如下:

  • 向 200 名相關者發出線上問卷調查,其中 70 人回答了問卷中的問題,前端人數佔 81.16%,如果你有興趣的話,也可以幫我填一下調查問卷 (https://www.wjx.cn/vm/Ombu9q1.aspx)

  • 資料收集日期:2021.09.21—2021.10.08

  • 目標群體:所有開發人員

  • 組織規模:不到 50 人,50 到 100人, 100人以上

你執行過 JavaScript 單元測試嗎?

調查中的另一個有趣的見解是,在大型組織中單元測試更受歡迎。其中一個原因可能是,由於大型組織需要處理大規模的產品,以及頻繁的功能迭代吧。這種持續的迭代方式,迫使他們進行自動化測試的投入。更具體地說,單元測試有助於增強產品的整體質量。

另外,報告顯示超 80% 人認為單元測試可以有效的提高質量,超 60% 人使用過 Jest 去編寫前端單元測試,超 40% 的人認為單元測試覆蓋率是重要的且覆蓋率應該大於 80%。

常見單元測試工具

目前用的最多的前端單元測試框架主要有 Mocha (https://mochajs.cn/)、Jest (https://www.jestjs.cn/),但我推薦你使用 Jest,因為 Jest 和 Mocha 相比,無論從 github starts & issues 量,npm下載量相比,都有明顯優勢。

github stars 以及 npm 下載量的實時資料,參見:jest vs mocha (https://www.npmtrends.com/jest-vs-mocha) 截圖日期為 2021.11.25

Github stars & issues

npm 下載量

Jest 的下載量較大,一部分原因是因為 create-react-app 腳手架預設內建了 Jest, 而大部分 react 專案都是用它生成的。

從 github starts & issues 以及 npm 下載量角度來看,Jest 的關注度更高,社群也更活躍

框架對比

框架 斷言 非同步 程式碼覆蓋率
Mocha 不支援(需要其他庫支援) 友好 不支援(需要其他庫支援)
Jest 預設支援 友好 支援
  • Mocha 生態好,但是需要較多的配置來實現高擴充套件性

  • Jest 開箱即用

比如對 sum 函式寫用例

./sum.js

1 function sum(a, b) {
2   return a + b;
3 }
4  
5 module.exports = sum;

  

Mocha + Chai 方式

Mocha 需要引入 chai 或則其他斷言庫去斷言, 如果你需要檢視覆蓋率報告你還需要安裝 nyc 或者其他覆蓋率工具

./test/sum.test.js

const { expect, assert } = require('chai');
const sum = require('../sum');
 
describe('sum', function() {
  it('adds 1 + 2 to equal 3', () => {
    assert(sum(1, 2) === 3);
  });
});

 

Jest 方式

Jest 預設支援斷言,同時預設支援覆蓋率測試

./test/sum.test.js

const sum = require('./sum');
 
describe('sum function test', () => {
  it('sum(1, 2) === 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
  
  // 這裡 test 和 it 沒有明顯區別,it 是指: it should xxx, test 是指 test xxx
  test('sum(1, 2) === 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
})

 

可見無論是受歡迎度和寫法上,Jest 都有很大的優勢,因此推薦你使用開箱即用的 Jest

如何開始?

1.安裝依賴

npm install --save-dev jest

2.簡單的例子

首先,建立一個 sum.js 檔案

./sum.js

function sum(a, b) {
  return a + b;
}
 
module.exports = sum;

 

建立一個名為 sum.test.js 的檔案,這個檔案包含了實際測試內容:

./test/sum.test.js

const sum = require('../sum');
 
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

 

將下面的配置部分新增到你的 package.json 裡面

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

 

執行 npm run test ,jest 將列印下面這個訊息

3.不支援部分 ES6 語法

nodejs 採用的是 CommonJS 的模組化規範,使用 require 引入模組;而 import 是 ES6 的模組化規範關鍵字。想要使用 import,必須引入 babel 轉義支援,通過 babel 進行編譯,使其變成 node 的模組化程式碼

如以下檔案改寫成 ES6 寫法後,執行 npm run test將會報錯

./sum.js

export function sum(a, b) {
  return a + b;
}

 

./test/sum.test.js

import { sum } from '../sum';
 
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

 

報錯

為了能使用這些新特性,我們就需要使用 babel 把 ES6 轉成 ES5 語法

解決辦法

安裝依賴

npm install --save-dev @babel/core @babel/preset-env

根目錄加入.babelrc

{   "presets": ["@babel/preset-env"] }

再次執行 npm run test ,問題解決

原理

jest 執行時內部先執行( jest-babel ),檢測是否安裝 babel-core,然後取 .babelrc 中的配置執行測試之前結合 babel 先把測試用例程式碼轉換一遍然後再進行測試

4.測試 ts 檔案

jest 需要藉助 .babelrc 去解析 TypeScript 檔案再進行測試

安裝依賴

npm install --save-dev @babel/preset-typescript

**改寫 **.babelrc

{   "presets": ["@babel/preset-env", "@babel/preset-typescript"] }

為了解決編輯器對 jest 斷言方法的型別報錯,如 test、expect 的報錯,你還需要安裝

npm install --save-dev @types/jest

./get.ts

/**
 * 訪問巢狀物件,避免程式碼中出現類似 user && user.personalInfo ? user.personalInfo.name : null 的程式碼
 */
export function get<T>(object: any, path: Array<number | string>, defaultValue?: T) : T {
  const result = path.reduce((obj, key) => obj !== undefined ? obj[key] : undefined, object);
 
  return result !== undefined ? result : defaultValue;
}

 

./test/get.test.ts

import { get } from './get';
 
test('測試巢狀物件存在的可列舉屬性 line1', () => {
  expect(get({
    id: 101,
    email: '[email protected]',
    personalInfo: {
      name: 'Jack',
      address: {
        line1: 'westwish st',
        line2: 'washmasher',
        city: 'wallas',
        state: 'WX'
      }
    }
  }, ['personalInfo', 'address', 'line1'])).toBe('westwish st');
});

 

執行 npm run test

5.持續監聽

為了提高效率,可以通過加啟動引數的方式讓 jest 持續監聽檔案的修改,而不需要每次修改完再重新執行測試用例

改寫 package.json

"scripts": {     "test": "jest --watchAll"   },

效果

5.生成測試覆蓋率報告

什麼是單元測試覆蓋率?

單元測試覆蓋率是一種軟體測試的度量指標,指在所有功能程式碼中,完成了單元測試的程式碼所佔的比例。有很多自動化測試框架工具可以提供這一統計資料,其中最基礎的計算方式為:

單元測試覆蓋率 = 被測程式碼行數 / 參測程式碼總行數 * 100%

如何生成?

加入 jest.config.js  檔案

module.exports = {
  // 是否顯示覆蓋率報告
  collectCoverage: true,
  // 告訴 jest 哪些檔案需要經過單元測試測試
  collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'],
}

 

再次執行效果

引數解讀

引數名 含義 說明
% stmts 語句覆蓋率 是不是每個語句都執行了?
% Branch 分支覆蓋率 是不是每個 if 程式碼塊都執行了?
% Funcs 函式覆蓋率 是不是每個函式都呼叫了?
% Lines 行覆蓋率 是不是每一行都執行了?

設定單元測試覆蓋率閥值

個人認為既然在專案中集成了單元測試,那麼非常有必要關注單元測試的質量,而覆蓋率則一定程度上客觀的反映了單測的質量,同時我們還可以通過設定單元測試閥值的方式提示使用者是否達到了預期質量。

jest.config.js  檔案

module.exports = {
  collectCoverage: true, // 是否顯示覆蓋率報告
  collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'], // 告訴 jest 哪些檔案需要經過單元測試測試
  coverageThreshold: {
    global: {
      statements: 90, // 保證每個語句都執行了
      functions: 90, // 保證每個函式都呼叫了
      branches: 90, // 保證每個 if 等分支程式碼都執行了
    },
  },

 

上述閥值要求我們的測試用例足夠充分,如果我們的用例沒有足夠充分,則下面的報錯將會幫助你去完善

6.如何編寫單元測試

下面我們以 fetchEnv 方法作為案例,編寫一套完整的單元測試用例供讀者參考

編寫 fetchEnv 方法

./src/utils/fetchEnv.ts  檔案

/**
* 環境引數列舉
*/
enum IEnvEnum {
DEV = 'dev', // 開發
TEST = 'test', // 測試
PRE = 'pre', // 預發
PROD = 'prod', // 生產
}

/**
* 根據連結獲取當前環境引數
* @param {string?} url 資源連結
* @returns {IEnvEnum} 環境引數
*/
export function fetchEnv(url: string): IEnvEnum {
const envs = [IEnvEnum.DEV, IEnvEnum.TEST, IEnvEnum.PRE];

return envs.find((env) => url.includes(env)) || IEnvEnum.PROD;
}

 

編寫對應的單元測試

./test/fetchEnv.test.ts  檔案

import { fetchEnv } from '../src/utils/fetchEnv';
 
describe('fetchEnv', () => {
  it ('判斷是否 dev 環境', () => {
    expect(fetchEnv('https://www.imooc.dev.com/')).toBe('dev');
  });
 
  it ('判斷是否 test 環境', () => {
    expect(fetchEnv('https://www.imooc.test.com/')).toBe('test');
  });
 
  it ('判斷是否 pre 環境', () => {
    expect(fetchEnv('https://www.imooc.pre.com/')).toBe('pre');
  });
 
  it ('判斷是否 prod 環境', () => {
    expect(fetchEnv('https://www.imooc.prod.com/')).toBe('prod');
  });
 
  it ('判斷是否 prod 環境', () => {
    expect(fetchEnv('https://www.imooc.com/')).toBe('prod');
  });
});

 

執行結果

7.常用斷言方法

關於斷言方法有很多,這裡僅摘出常用方法,如果你想了解更多,你可以去 Jest 官網 API (https://www.jestjs.cn/docs/expect) 部分檢視

.not 修飾符允許你測試結果不等於某個值的情況

./test/sum.test.js

import { sum } from './sum';
 
test('sum(2, 4) 不等於 5', () => {
  expect(sum(2, 4)).not.toBe(5);
})

 

.toEqual 匹配器會遞迴的檢查物件所有屬性和屬性值是否相等,常用來檢測引用型別

./src/utils/userInfo.js

export const getUserInfo = () => {
  return {
    name: 'moji',
    age: 24,
  }
}

 

./test/userInfo.test.js

import { getUserInfo }  from '../src/userInfo.js';
 
test('getUserInfo()返回的物件深度相等', () => {
  expect(getUserInfo()).toEqual(getUserInfo());
})
 
test('getUserInfo()返回的物件記憶體地址不同', () => {
  expect(getUserInfo()).not.toBe(getUserInfo());
})

 

.toHaveLength 可以很方便的用來測試字串和陣列型別的長度是否滿足預期

./src/utils/getIntArray.js

export const getIntArray = (num) => {
  if (!Number.isInteger(num)) {
    throw Error('"getIntArray"只接受整數型別的引數');
  }
  
  return [...new Array(num).keys()];
};

 

./test/getIntArray.test.js

./test/getIntArray.test.js
import { getIntArray }  from '../src/utils/getIntArray';
 
test('getIntArray(3)返回的陣列長度應該為3', () => {
  expect(getIntArray(3)).toHaveLength(3);
})

 

.toThorw 能夠讓我們測試被測試方法是否按照預期丟擲異常

但是需要注意的是:我們必須使用一個函式將被測試的函式做一個包裝,正如下面 getIntArrayWrapFn 所做的那樣,否則會因為函式丟擲錯誤導致該斷言失敗。

./test/getIntArray.test.js

import { getIntArray }  from '../src/utils/getIntArray';
 
test('getIntArray(3.3)應該丟擲錯誤', () => {
  function getIntArrayWrapFn() {
    getIntArray(3.3);
  }
  
  expect(getIntArrayWrapFn).toThrow('"getIntArray"只接受整數型別的引數');
})

 

.toMatch 傳入一個正則表示式,它允許我們來進行字串型別的正則匹配

./test/userInfo.test.js

import { getUserInfo }  from '../src/utils/userInfo.js';
 
test("getUserInfo().name 應該包含'mo'", () => {
  expect(getUserInfo().name).toMatch(/mo/i);
})

 

測試非同步函式

./servers/fetchUser.js

/** 
 * 獲取使用者資訊
*/
export const fetchUser = () => {
  return new Promise((resole) => {
    setTimeout(() => {
      resole({
        name: 'moji',
        age: 24,
      })
    }, 2000)
  })
}

 

./test/fetchUser.test.js

import { fetchUser } from '../src/fetchUser';
 
test('fetchUser() 可以請求到一個使用者名稱字為 moji', async () => {
  const data =  await fetchUser();
 
  expect(data.name).toBe('moji')
})

 

這裡你可能看到這樣一條報錯

這是因為 @babel/preset-env 不支援 async await 導致的,這時候就需要對 babel 配置進行增強,可以安裝 @babel/plugin-transform-runtime 這個外掛解決

npm install --save-dev @babel/plugin-transform-runtime

 

同時改寫 .babelrc

{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"],
  "plugins": ["@babel/plugin-transform-runtime"]
}

 

再次執行就不會出現報錯了

.toContain 匹配物件中是否包含

./test/toContain.test.js

const names = ['liam', 'jim', 'bart'];
 
test('匹配物件是否包含', () => {
  expect(names).toContain('jim');
})

 

檢查一些特殊的值(null,undefined 和 boolean)

toBeNull 僅匹配 null
toBeUndefined 僅匹配 undefined
toBeDefined 與…相反 toBeUndefined
toBeTruthy 匹配 if 語句視為 true 的任何內容
toBeFalsy 匹配 if 語句視為 false 的任何內容
 
檢查數字型別(number)
toBeGreaterThan 大於
toBeGreaterThanOrEqual 至少(大於等於)
toBeLessThan 小於
toBeLessThanOrEqual 最多(小於等於)
toBeCloseTo 用來匹配浮點數(帶小數點的相等)

 

總結

以上就是文章全部內容,相信你閱讀完這篇文章後,已經掌握了前端單元測試的基本知識,甚至可以按照文章教學步驟,現在就可以在你的專案中接入單元測試。同時在閱讀過程中如果你有任何問題,或者有更好見解,更好的框架推薦,歡迎你在評論區留言!

轉自https://blog.csdn.net/xgangzai/article/details/122227545