jest java_✅使用jest进行测试驱动开发
前言
本文將使用jest進(jìn)行測(cè)試驅(qū)動(dòng)開(kāi)發(fā)的示例,源碼在github。重點(diǎn)說(shuō)明在開(kāi)發(fā)中引入單元測(cè)試后開(kāi)發(fā)過(guò)程,以及測(cè)試先行的開(kāi)發(fā)思路。
本文的重點(diǎn)是過(guò)程以及思維方法,框架以及用法不是重點(diǎn)。
本文使用的編程語(yǔ)言是javascript,思路對(duì)其他語(yǔ)言也是適用的。
本文主要以函數(shù)作為測(cè)試對(duì)象。
環(huán)境搭建
假設(shè)項(xiàng)目結(jié)構(gòu)為
.
├── README.md
├── package.json
├── src
├── test
└── yarn.lock安裝依賴
yarn add --dev jest打開(kāi)package.json, 修改scripts字段
"scripts": {
"test": "jest"
}
之后把測(cè)試文件放在test文件夾下,使用yarn test 即可查看測(cè)試結(jié)果
開(kāi)發(fā)
現(xiàn)在要開(kāi)發(fā)一個(gè)函數(shù),根據(jù)傳入的文件名判斷是否為shell文件。
先做好約定:shell文件應(yīng)該以 .sh 結(jié)尾
shell文件不以 . 開(kāi)頭
函數(shù)為名 isShellFile
下面來(lái)看下開(kāi)發(fā)步驟是怎么樣的。
文件初始化
在src目錄下新建 isShellFile.js
touch isShellFile.js
然后一行代碼也不寫(xiě),在test目錄下新建 isShellFile.test.js
可以注意到,測(cè)試文件的名與源文件名類似,只是中間多了個(gè) .test
touch isShellFile.test.js
第一個(gè)用例
打開(kāi)測(cè)試文件 test/isShellFile.test.js ,編寫(xiě)第一個(gè)用例,也是最普通的一個(gè): bash.sh
const isShellFile = require('../src/isShellFile')
test('isShellFile', () => {
// 調(diào)用函數(shù),期望它返回值為 true expect(isShellFile('bash.sh')).toBeTruthy()
})
運(yùn)行 yarn test , 結(jié)果如下:
FAIL test/isShellFile.test.js
? isShellFile (2ms)
● isShellFile
TypeError: isShellFile is not a function
^^^
3 | test('isShellFile', () => {
4 |
> 5 | expect(isShellFile('bash.sh')).toBeTruthy()
| ^
6 | })
失敗是意料之中的,因?yàn)?src/isShellFile.js 一行代碼也沒(méi)寫(xiě),所以測(cè)試代碼中第5行 isShellFile 無(wú)法進(jìn)行函數(shù)調(diào)用。
完善源文件src/isShellFile.js
module.exports = function(filename) {
}
這樣 isShellFile 就可以作為函數(shù)被調(diào)用了。
再運(yùn)行 yarn test
FAIL test/isShellFile.test.js
? isShellFile (7ms)
● isShellFile
expect(received).toBeTruthy()
^^^
Received: undefined
3 | test('isShellFile', () => {
4 |
> 5 | expect(isShellFile('bash.sh')).toBeTruthy()
| ^
6 | })
又報(bào)錯(cuò)了,但這次報(bào)錯(cuò)原因跟上次不同,說(shuō)明有進(jìn)步。
這次報(bào)錯(cuò)原因是,期望函數(shù)調(diào)用返回值為真 , 但實(shí)際沒(méi)有返回真 。
這是當(dāng)然的,因?yàn)樵谠次募?#xff0c;根本沒(méi)有寫(xiě)返回語(yǔ)句。
為了讓測(cè)試通過(guò),修改 src/isShellFile.js
module.exports = function(filename) {
+ return true
}
運(yùn)行 yarn test , 測(cè)試通過(guò)了!
PASS test/isShellFile.test.js
? isShellFile (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.548s
Ran all test suites.
把上述修改,提交到版本控制系統(tǒng)中。
git add package.json yarn.lock src test
git commit -m 'feat: init jest test case'
第二個(gè)用例
觀察我們的測(cè)試用例,發(fā)現(xiàn)太簡(jiǎn)單了,只有正面的用例,沒(méi)有反面的、異常的用例
test('isShellFile', () => {
expect(isShellFile('bash.sh')).toBeTruthy()
})
在 test/isShellFile.test.js 添加一個(gè)反面的用例
test('isShellFile', () => {
expect(isShellFile('bash.sh')).toBeTruthy()
+ expect(isShellFile('bash.txt')).toBeFalsy()
})
運(yùn)行 yarn test
(可以發(fā)現(xiàn),在開(kāi)發(fā)過(guò)程中需要反復(fù)執(zhí)行上述命令,有個(gè)偷懶的辦法,執(zhí)行yarn test --watch,即可監(jiān)聽(tīng)文件變化,自動(dòng)執(zhí)行測(cè)試用例)
FAIL test/isShellFile.test.js
? isShellFile (6ms)
● isShellFile
expect(received).toBeFalsy()
^^^
Received: true
4 |
5 | expect(isShellFile('bash.sh')).toBeTruthy()
> 6 | expect(isShellFile('bash.txt')).toBeFalsy()
| ^
7 | })
報(bào)錯(cuò)了,期望返回假,但函數(shù)返回的是真。這是因?yàn)?#xff0c;源文件中, isShellFile 函數(shù)永遠(yuǎn)返回真!
完善 src/isShellFile.js 邏輯
module.exports = function(filename) {
- return true;
+ return filename.indexOf('.sh') > -1
};
測(cè)試通過(guò)了
PASS test/isShellFile.test.js
? isShellFile (4ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.568s
Ran all test suites.
把上述修改提交到版本控制系統(tǒng)
git commit -am 'fix: 函數(shù)永遠(yuǎn)返回真的bug'
第三個(gè)用例
我們?cè)偬砑右粋€(gè)用例,這次考慮特殊情況: .sh 這種文件,不算是shell文件。
修改 test/isShellFile.test.js
expect(isShellFile("bash.sh")).toBeTruthy();
expect(isShellFile("bash.txt")).toBeFalsy();
+ expect(isShellFile('.sh')).toBeFalsy()
測(cè)試不通過(guò)
FAIL test/isShellFile.test.js
? isShellFile (8ms)
● isShellFile
expect(received).toBeFalsy()
^^^
Received: true
5 | expect(isShellFile("bash.sh")).toBeTruthy();
6 | expect(isShellFile("bash.txt")).toBeFalsy();
> 7 | expect(isShellFile('.sh')).toBeFalsy()
| ^
8 | });
說(shuō)明邏輯待完善,修改 src/isShellFile.js
module.exports = function(filename) {
- return filename.indexOf(".sh") > -1;
+ let index = filename.indexOf(".sh");
+ return index > -1 && index != 0;
};
測(cè)試通過(guò)(為精簡(jiǎn)文章內(nèi)容,后面不再展示測(cè)試通過(guò)的輸出),提交代碼。
git commit -am 'fix: .sh應(yīng)該返回false'
第四個(gè)用例
按照第三個(gè)用例的邏輯, .bash.sh 也不應(yīng)該是shell文件,那么函數(shù)是否能正確判斷呢,測(cè)試便知。
修改 test/isShellFile.test.js
expect(isShellFile('.sh')).toBeFalsy()
+ expect(isShellFile('.bash.sh')).toBeFalsy()
測(cè)試不通過(guò)
FAIL test/isShellFile.test.js
? isShellFile (3ms)
● isShellFile
expect(received).toBeFalsy()
^^^
Received: true
6 | expect(isShellFile("bash.txt")).toBeFalsy();
7 | expect(isShellFile('.sh')).toBeFalsy()
> 8 | expect(isShellFile('.bash.sh')).toBeFalsy()
| ^
9 | });
說(shuō)明邏輯待完善,修改 src/isShellFile.js
module.exports = function(filename) {
let index = filename.indexOf(".sh");
- return index > -1 && index != 0;
+ return !filename.startsWith('.') && index > -1;
};
測(cè)試通過(guò),提交代碼。
git commit -am 'fix: .開(kāi)頭的文件不算sh文件'
第五個(gè)用例
再考慮一種情況,如果 .sh 出現(xiàn)在中間呢?如 bash.sh.txt , 它不應(yīng)該是shell文件,來(lái)看看函數(shù)是否能通過(guò)測(cè)試。
修改 test/isShellFile.test.js
expect(isShellFile('.bash.sh')).toBeFalsy()
+ expect(isShellFile('bash.sh.txt')).toBeFalsy()
測(cè)試不通過(guò)
FAIL test/isShellFile.test.js
? isShellFile (5ms)
● isShellFile
expect(received).toBeFalsy()
^^^
Received: true
7 | expect(isShellFile('.sh')).toBeFalsy()
8 | expect(isShellFile('.bash.sh')).toBeFalsy()
> 9 | expect(isShellFile('bash.sh.txt')).toBeFalsy()
| ^
10 | });
說(shuō)明邏輯待完善,修改 src/isShellFile.js
module.exports = function(filename) {
- let index = filename.indexOf(".sh");
- return !filename.startsWith('.') && index > -1;
+ let index = filename.lastIndexOf(".");
+ return !filename.startsWith('.') && filename.substr(index) == '.sh';
};
測(cè)試通過(guò),提交代碼。
git commit -am 'fix: .sh必須在結(jié)尾'
重構(gòu)
我們來(lái)觀察目前 src/isShellFile.js 的函數(shù)邏輯
module.exports = function(filename) {
let index = filename.lastIndexOf(".");
return !filename.startsWith('.') && filename.substr(index) == '.sh';
};
對(duì)于 .bashrc 這樣的文件,并不是shell文件,因?yàn)樗且?. 開(kāi)頭的。
則通過(guò) filename.startsWith('.') 判斷即可,前面的函數(shù)調(diào)用 filename.lastIndexOf(".") 是多余的。也即,目前的函數(shù)判斷邏輯不夠簡(jiǎn)明。
下面是一種優(yōu)化思路:
module.exports = function(filename) {
return !filename.startsWith('.') && filename.substr(filename.lastIndexOf(".")) == '.sh';
};
測(cè)試通過(guò),提交代碼
git commit -am 'refactor: 優(yōu)化邏輯'
注意,這個(gè)重構(gòu)示例的重點(diǎn)是:先完成功能,再重構(gòu)
重構(gòu)必須要有測(cè)試用例,且確保重構(gòu)后全部測(cè)試用例通過(guò)
至于其他方面,見(jiàn)仁見(jiàn)智,并不是重點(diǎn)。
結(jié)論
本文通過(guò)代碼實(shí)例,踐行了測(cè)試先行的理念。
文中的代碼實(shí)現(xiàn)不是重點(diǎn),而是開(kāi)發(fā)過(guò)程。
文中 文件初始化 及 第一個(gè)用例 的內(nèi)容,尤其值得回味,它體現(xiàn)了兩個(gè)思路:總是在有一個(gè)失敗的單元測(cè)試后才開(kāi)始編碼
用必要的最小代碼讓測(cè)試通過(guò)
總的來(lái)看,TDD總是處于一個(gè)循環(huán)中:編寫(xiě)用例
測(cè)試失敗
編寫(xiě)代碼
測(cè)試成功
提交代碼
重復(fù)以上
通過(guò)這樣,功能的實(shí)現(xiàn)每次都是最小成本的,功能也是有步驟地、通過(guò)迭代完成的,而不是一步登天。
更關(guān)鍵的是,完善的測(cè)試用例,是開(kāi)發(fā)者的“守護(hù)天使”,有了它們,以后在添加新功能時(shí),修改/重構(gòu)代碼都有了可靠的保障,讓開(kāi)發(fā)者可以充滿信心,code with confidence !
擴(kuò)展
使用babel
要想使用import/export語(yǔ)法,需要安裝babel相關(guān)依賴安裝依賴
yarn add --dev babel-jest @babel/core @babel/preset-env在項(xiàng)目根路徑新增配置文件 babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};重新啟動(dòng)測(cè)試
yarn test --watch
為什么使用jest
因?yàn)檫@是vue官方工具鏈的一部分, 同時(shí)也可以為后續(xù)的組件測(cè)試作準(zhǔn)備。
總結(jié)
以上是生活随笔為你收集整理的jest java_✅使用jest进行测试驱动开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java set iterator_Ja
- 下一篇: java怎么在记事本里写过运行_[置顶]