什麼做這個事情
我在我寫的文章裏面多次提到單元測試的重要性。重要的事情說三遍“單測很重要”、“單測很重要”、“單測很重要”。
單純說這句話沒公信力和權威性,那我舉例子來說明吧。
場景1
某業務線在不斷的版本迭代,在版本6的時候發現功能 A 的代碼太亂太多了。小劉同學打算重構,他辛辛苦苦解決後,打算提交給測試工程師進行測試。測試工程師說“小劉,你這個代碼全是 Bug 呀,我點進去就 Crash”。小劉聽到後很尷尬,心裏想“功能 A 的代碼我寫的很小心,一行行檢查過去的,不可能有問題”。測試說“我點擊商品頁,點擊加入購物車,馬上 Crash 了”。你這個測試被打回,阻塞主流程了。小劉想了想才發現他自己開發的模塊是沒有問題的,但是他在重構功能 A 的時候不小心把依賴功能 A 的地方少傳遞了配置參數 😅
場景2
某公司基礎普通組有小劉同學,在設計某個新技術方案的時候,辛辛苦苦花了3天時間出了技術方案,他去找老闆聊,老闆讓他把思路描述下,再把設計的測試用例給一下。小劉吞吞吐吐講完了設計思路,但是他說還沒有設計測試用例。老闆說你沒測試用例,我怎麼 review 你的設計,一行行看代碼理解邏輯嗎?一句句聽你的設計判斷有沒有問題嗎?以後你找我聽技術設計,你理好設計思路,和測試用例。我看看主流程和一些邊界的輸入輸出,確保這些東西正確那就是沒問題的。我沒有那麼多時間一行行看代碼。(說的也是,組長是 P9 忙得很)
場景3
某公司基礎普通組有小劉同學在做了移動端的 APM 監控和數據上報 SDK 的第一版,但是他很乖,寫好了單元測試。忘了說了小劉同學是負責 iOS 端的,同事做 Android 端的小張同學和他對應,不同就是他沒有寫單元測試的代碼。 需求下來了,需要迭代版本2,小劉和小張都開發好代碼了,需要進行測試。哈哈哈小劉樂壞了,他花了0.5天就測試結束,小張花了1.5天進行測試。爲什麼呢?小劉寫代碼都要寫單元測試代碼,小張不寫。雖然寫單元測試代碼可能需要花一點點時間。但是當新版本迭代的時候就不需要回頭繼續全量測試。他只要按下 Command + U
, Xcode 就會跑單元測試相關的代碼。
那單元測試的好處?😂 什麼?你還問好處,上面那麼清晰明瞭,那我再總結下:
- 確保你寫的代碼的每個分支都可以被覆蓋,防止線上代在用戶端,不小心執行到未知的分支裏面
- 在進行新版本迭代或者重構的時候,可以集中精力到新邏輯裏面,舊的邏輯可以用 UT 測試覆蓋率來確保
- 在團隊內進行 code review 或者 merge review 的時候,review 的人可以看代碼中主要邏輯,結合 ut 來判斷。
另外,測試來說一般是結合 CI、CD 的,像我們公司有自己的工具 cli 工具, iOS、Android、RN、React、Vue、Node 項目都一起處理,分析依賴、打包構建(打包系統根據工程特點調度特定打包機)、測試、hot fix、埋點統計、APM等等。
所以如果公司規模小,就寫好 UT 然後結合 lint 做一些處理,公司規模大、開發有能力則需要結合 ci、cd 將測試的能力結合進去。
另外 UT 是一道工序,最好在每個開發者寫代碼的時候做 MR,團隊內 MR +1 數大於3纔可以合併到分支,且 +1 的人裏面必須有一個同一個項目的同學,必須有一個同技術棧且比你高水平的人,MR 指出的問題修改好纔可以合併。且 MR 代碼不能太大,因爲太大,給你做 MR 的同學會很耗費精力。人家閱讀你代碼時間成本太大。
Node 側如何進行 UT 開發
Node 在大學三年級的時候就聽說了,也寫過,之前也用來寫過爬蟲、自動化腳本、cli 等,之前學習過如何在 Node 側寫 ut,這篇文章用來總結下。
舉個例子,有個 Node 工程,一個模塊的主要功能是獲取該目錄下的所有文件。開發代碼如下
// fetchCodeFiles.js
const fs = require('fs-extra'),
glob = require('glob')
const fetchCodeFiles = async (dirPath) => {
return new Promise((reslove, reject) => {
glob('**/*.?(sh|pch|json|xcconfig|mm|cpp|h|m)', { root: dirPath, cwd: dirPath, realpath: true }, (err, files) => {
if (err) reject(err)
reslove(files)
})
});
}
module.exports = fetchCodeFiles
單元測試該怎麼做?
- 在終端下切換到當前工程目錄,安裝
npm install yamljs --save
- 在工程根目錄下新建
test
文件夾 - 爲你需要的開發文件寫測試代碼。文件命名建議
模塊名稱.test.js
- 測試代碼需要引入
assert
。 - 通過
describe
方法、it
方法、assert
方法描寫測試代碼 - 爲了方便測試,在
package.json
文件中的scripts
下添加描述"test": "mocha"
- 爲了更方便,我使用的是 iterm2,在 .zshrc 文件裏設置別名
alias nt="node test"
提升效率的配置 .zshrc 可以查看文章: Mac 終端效率神技
上面開發代碼的測試代碼如下:
// fetchCodeFiles.test.js
const fetchCodeFiles = require('./../src/fetchCodeFiles'),
assert = require('assert')
describe("fetch all code files", () => {
describe("fetch all code files", () => {
it("should return 12 when code directory is '/Users/liubinpeng/Workspace/search_key/test/code'", (done) => {
done(assert(fetchCodeFiles('/Users/liubinpeng/Workspace/search_key/test/code')) === 12)
})
it("should return 4 when code directory is '/Users/liubinpeng/Workspace/search_key/test/code/Classes'", (done) => {
done(assert(fetchCodeFiles('/Users/liubinpeng/Workspace/search_key/test/code/Classes')) === 4)
})
it("should return 0 when code directory is '/Users/liubinpeng/Workspace/search_key/test/code/EmptyCodeFiles'", (done) => {
done(assert(fetchCodeFiles('/Users/liubinpeng/Workspace/search_key/test/code/EmptyCodeFiles')) === 0)
})
})
})
// package.json
{
"name": "search",
"version": "1.0.0",
"description": "des",
"main": "index.js",
"scripts": {
"test": "mocha",
"start": "node src/index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"fs-extra": "^8.1.0",
"glob": "^7.1.6",
"yamljs": "^0.3.0"
},
"devDependencies": {
"mocha": "^6.2.2"
}
}
運行結果
Mocha
本文主要想說明的是 UT 的重要性,以及 Node 測如何做單元測試。當然 UT 沒這麼簡單,具體深入的不是本文的終點,感興趣的可以查看 Mocha 這個項目的官方文檔。