目錄???????
什么是自動化測試
為什么要用前端自動化測試
前端自動化分類和思想
單元測試
集成測試
TDD 測試驅動開發(fā)(Test Driven Development)
BDD 行為驅動開發(fā)(Behavior Driven Development)
如何自己寫非框架測試用例
是否能簡化?
如何能清晰地看到我測的是哪個呢?
如何使用 Jest 測試框架進行自動化測試?
主流的前端自動化測試框架
Jasmine
MOCHA
Jest
準備工作 —— Jest 的配置
使用 babel 轉換來使用 ES6 形式的導入和導出
如何生成一個測試用例覆蓋率報告?
Jest 基礎匹配器
匹配器
命令行操作
異步測試
鉤子函數(shù)
分組方法 discribe
Mock
Mock 高階用法
Mock-timers
snapshot 快照
對 dom 節(jié)點測試
VSCode 插件
常用配置解讀
小結
代碼地址
參考文檔
什么是自動化測試
????????在軟件測試中,自動化測試指的是使用獨立于待測軟件的其他軟件來自動執(zhí)行測試、比較實際結果與預期并生成測試報告這一過程。在測試流程已經(jīng)確定后,測試自動化可以自動執(zhí)行的一些重復但必要的測試工作。也可以完成手動測試幾乎不可能完成的測試。對于持續(xù)交付和持續(xù)集成的開發(fā)方式而言,測試自動化是至關重要的。? ?——來自 WiKi 百科
為什么要用前端自動化測試
????????隨著前端項目的發(fā)展,其規(guī)模和功能日益增加。為了提高項目的穩(wěn)定性和可靠性,除了需要測試工程師外,前端自動化測試也成為了不可或缺的一環(huán)。采用前端自動化測試可以有效地提高代碼質量,降低出錯的概率,從而使項目更加健壯和更易維護。
前端自動化分類和思想
單元測試
????????又稱為模塊測試 ,是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。在前端中,一個函數(shù)、一個類、一個模塊文件,都可以進行單元測試,測試時每個模塊都是互不干擾的。
集成測試
????????是在單元測試的基礎上,測試再將所有的軟件單元按照概要設計規(guī)格說明的要求組裝成模塊、子系統(tǒng)或系統(tǒng)的過程中各部分工作是否達到或實現(xiàn)相應技術指標及要求的活動。用戶的開始操作到結束操作這一整個行為流程可以當作集成測試。
TDD 測試驅動開發(fā)(Test Driven Development)
開發(fā)流程:
TDD 是趨向于白盒測試,需要開發(fā)者對當前編寫的模塊思路足夠清晰。
優(yōu)勢:
-
長期減少回歸?
bug
。 -
代碼質量更好,可維護性高。
-
測試覆蓋率高(先寫測試用例,再實現(xiàn)功能)。
-
錯誤測試代碼不容易出現(xiàn)(測試在開發(fā)之前執(zhí)行)。
BDD 行為驅動開發(fā)(Behavior Driven Development)
開發(fā)流程:
BDD 趨向于黑盒測試,只關注用戶的一整套行為流程下來是否會成功。
優(yōu)勢:
-
對于用戶行為的整個流程把控程度較高,對于開發(fā)人員來說這樣安全感高。
如何自己寫非框架測試用例
????????不使用測試框架,我們該如何測試自己的模塊呢?如果我們想要測試下面的代碼,應該需要兩個值,一個是?期望值
?,另一個是函數(shù)執(zhí)行的?結果值
?,我們需要對比兩個值來進行判斷當前函數(shù)是否通過了測試用例。
//?index.js
function?ZcyZooTeam(str)?{
??return?'Zcy'?+?str;
}
????????需要下面的?if / else
?進行判斷當前的期望值?value
?和結果值?result
?是否相等,如果相等說明我們的測試用例通過了。我們將這兩段代碼復制到瀏覽器中,下面的執(zhí)行不會通過,并會拋出錯誤,只有我們將傳入值改為?ZooTeam
?才會成功執(zhí)行。
//?no-jest.js
const?result?=?ZcyZooTeam('Zero');
const?value?=?'ZooTeam';
if(result?!==?value)?{
??throw?Error(`ZcyZooTeam?結果應為${value},?但實際結果為${result}`);
}
是否能簡化?
????????如果我們有多個函數(shù)需要測試,你應該不想寫許多個?if / else
?代碼塊吧?所以我們要將上面的代碼塊進行優(yōu)化成一個函數(shù)。
//?no-jest.js
function?expect(result)?{
??return?{
????//?用于判斷是否為期望值
????toBe(value)?{
??????if(result?!==?value)?{
????????throw?Error(`結果應為${value},?但實際結果為${result}`);
??????}
??????console.log('測試通過!');
????}
??}
}
//?執(zhí)行測試
expect(ZcyZooTeam('Zero')).toBe('ZcyZooTeam');
經(jīng)過上面的封裝,我們就可以只寫一行代碼進行測試了!
如何能清晰地看到我測的是哪個呢?
????????雖然上面的封裝只需要書寫一行代碼就可以測試了,但是我們不知道執(zhí)行結果和測試用例之間的對應關系,我們需要輸出的文字來告訴我們當前是哪個測試用例執(zhí)行了。
//?no-jest.js
//?再封裝如下方法
function?test(msg,?fn)?{
??try?{
????fn();
????console.log(msg?+?'測試通過!');
??}?catch?(error)?{
????console.log(msg?+?'測試未通過!'?+?error);
??}
}
test('測試ZcyZooTeam',?()?=>?{
??expect(ZcyZooTeam('Zero')).toBe('ZcyZooTeam')
})
成功和失敗都會進行提示,這樣我們就可以知道當前是哪個測試用例成功/失敗了。
????Jest
?的書寫方式也是同上,如果上面的一整套代碼了解了的話,你已經(jīng)可以寫?Jest
?的測試腳本了,下面將進入?Jest
?的配置。
如何使用 Jest 測試框架進行自動化測試?
主流的前端自動化測試框架
Jasmine
Jasmine
?優(yōu)點:易于學習和使用,支持異步測試,可以在瀏覽器和?Node.js
?環(huán)境中運行,可以生成易于閱讀的測試報告,可以與其他庫和框架集成。
MOCHA
MOCHA
?優(yōu)點:支持異步測試和?Promise
?,可以在瀏覽器和?Node.js
?環(huán)境中運行,可以與其他庫和框架集成,可以生成易于閱讀的測試報告,可以使用各種插件和擴展來增強其功能。
Jest
Jest
?是針對模塊進行測試,單元測試對單個模塊進行測試,集成測試對多個模塊進行測試。
Jest
?優(yōu)點:速度快(單模塊測試時,執(zhí)行過的模塊不會重復執(zhí)行),API
簡單,易配置,隔離性好(執(zhí)行環(huán)境相對隔離,每個文件單獨隔離互不干擾),監(jiān)控模式(更靈活的運行各種測試用例),適配編輯器多,Snapshot
(快照),多項目運行(后臺前臺測試用例并行測試),生成可視化覆蓋率簡單,Mock
?豐富。
準備工作 —— Jest 的配置
npm?i?jest?--save-D
//?初始化?jest?的配置文件
npx?jest?--init
//?你將在那個環(huán)境進行測試,回車即可選擇
//?第一個是?node?環(huán)境、第二個是瀏覽器環(huán)境
??Choose?the?test?environment?that?will?be?used?for?testing???-?Use?arrow-keys.?Return?to?submit.
????node
????jsdom?(browser-like)
//?是否需要?jest?生成測試覆蓋率報告
??Do?you?want?Jest?to?add?coverage?reports????(y/N)
//?是否需要在測試結束后清除模擬調用
??Automatically?clear?mock?calls?and?instances?between?every?test????(y/N)
//?創(chuàng)建?jest.config.js?文件
????Configuration?file?created?at?/Users/zcy1/Desktop/demo/auto-test-jest-demo/jest.config.js
????????以上方法執(zhí)行結束后,會生成一個?jest.config.js
?文件,里面包含了?Jest
?的配置項,每個配置項都會帶有描述,在初始化的兩個配置也會體現(xiàn)在配置文件中
使用 babel 轉換來使用 ES6 形式的導入和導出
//?.babelrc
//?如果想用?es6?的形式導出,需要使用?babel?插件進行轉換
//?@babel/core??@babel/preset-env
//?創(chuàng)建?.babelrc?文件
//?為了在?node?環(huán)境下使用?es6?的導出,需要使用?babel?進行轉換
{
??//?設置插件集合
??"presets":?[
????//?使用當前插件,可以進行轉換
????//?數(shù)組的第二項為插件的配置項
????[
??????"@babel/preset-env",?{
????????//?根據(jù)?node?的版本號來結合插件對代碼進行轉換
????????"targets":?{
??????????"node":?"current"
????????}
??????}
????]
??]
}
????????配置好后需要將?package.json
?中的?test
?命令的?value
?改為?jest --watchAll
,代表監(jiān)聽所有有修改的測試文件,然后控制臺執(zhí)行?npm run test
?就可以執(zhí)行測試用例了。
Jest 啟動時會進行如下流程:
-
·
npm run test
-
·
jest (babel-jest)??
檢測當前環(huán)境是否安裝了?babel
-
·如果安裝了則會去?
babelrc
?中取配置 -
·取到后執(zhí)行代碼轉換
-
·最后再執(zhí)行轉化過的測試用例代碼
如何生成一個測試用例覆蓋率報告?
經(jīng)過上面的?Jest
?配置,我們就可以通過下面的?npx
?命令來生成測試覆蓋率報告了
npx?jest?--coverage
????????會生成一個名為?coverage
?的文件夾,打開里面的?html
?就可以看到你的覆蓋率,其中?Statements
?是語句覆蓋率(每個語句是否執(zhí)行),Branches
?是分支覆蓋率(每個?if
塊是否執(zhí)行),Functions
是函數(shù)覆蓋率(每個函數(shù)是否執(zhí)行),Lines
?是行覆蓋率(每行是否執(zhí)行),通過修改?coverageDirectory
?的值可以改變測試覆蓋率生成文件夾的名字
Jest 基礎匹配器
????????上面我們說過了,Jest
?的用法和我們封裝的那幾個函數(shù)是一樣的,都是執(zhí)行?test
?函數(shù)并向函數(shù)中傳遞參數(shù),第一個參數(shù)是你當前測試用例的描述,第二個參數(shù)是需要執(zhí)行的匹配規(guī)則。
匹配器
toBe
toBe 匹配器,期待是否與匹配器中的值相等 相當于 object.is ===
//?jest.test.js
test("測試",?()?=>?{
??expect(1).toBe(1);??//?通過
??const?a?=?{?name:?'Zero'?};
??//?因為?a?的引用地址,和?toBe?中對象的引用地址不一致,會導致測試不通過,需要使用其他的匹配器
??expect(a).toBe({?name:?'Zero'?});??//?失敗
});
toEqual
toEqual 匹配器,只會匹配對象中的內容是否相等。
//?jest.test.js
test('測試對象相等',?()?=>?{
??const?a?=?{?name:?'Zero'?};
??expect(a).toEqual({?name:?'Zero'?});??//?斷言
})
toBeNull
toBeNull 匹配器,可以判斷變量是否為 null ,只能匹配 null。
//?jest.test.js
test('測試是否為null',?()?=>?{
??const?a?=?null;
??expect(a).toBeNull();
})
toBeUndefined
toBeUndefined 匹配器,可以判斷變量是否為 undefined ,只能匹配 undefined。
//?jest.test.js
test('測試是否為undefined',?()?=>?{
??const?a?=?undefined;
??expect(a).toBeUndefined();
})
toBeDefined
toBeDefined 匹配器,希望被測試的值是定義好的。
//?jest.test.js
test('測試變量是否定義過',?()?=>?{
??const?a?=?'';
??expect(a).toBeDefined();
})
toBeTruthy
toBeTruthy 匹配器,可以判斷變量是否為真值,會對非 bool 值進行轉換。
//?jest.test.js
test('測試變量真值',?()?=>?{
??const?a?=?'123';
??expect(a).toBeTruthy();
})
toBeFalsy
toBeFalsy 匹配器,可以判斷變量是否為假值,會對非 bool 值進行轉換。
//?jest.test.js
test('測試變量假值',?()?=>?{
??const?a?=?'';
??expect(a).toBeFalsy();
})
not修飾符
not 匹配器,可以將匹配后的結果進行取反。
//?jest.test.js
test('測試變量不是假值',?()?=>?{
??const?a?=?'1';
??expect(a).not.toBeFalsy();
})
toBeGreaterThan
toBeGreaterThan 匹配器,期望值是否大于匹配器的參數(shù)。
//?jest.test.js
test('是否大于?a?的數(shù)字',?()?=>?{
??const?a?=?123;
??expect(a).toBeGreaterThan(1);
})
toBeLessThan
toBeLessThan 匹配器,期望值是否小于匹配器的參數(shù)。
//?jest.test.js
test('是否小于?a?的數(shù)字',?()?=>?{
??const?a?=?0;
??expect(a).toBeLessThan(1);
})
toBeGreaterThanOrEqual
toBeGreaterThanOrEqual 匹配器,期望值是否大于或等于匹配器的參數(shù)。
//?jest.test.js
test('是否大于等于?a?的數(shù)字',?()?=>?{
??//?toBeLessOrEqual?匹配器,與之相反
??const?a?=?123;
??expect(a).toBeGreaterThanOrEqual(1);
})
toBeCloseTo
js 中,浮點數(shù)值在相加時不準確,使用 toBeCloseTo 匹配器解決,趨近于 0.3。
//?jest.test.js
test('是否大于等于?a?的數(shù)字',?()?=>?{
??const?a1?=?0.1;
??const?a2?=?0.2;
??expect(a1?+?a2).toBeCloseTo(0.3);
})
toMatch
toMatch 匹配器,匹配當前字符串中是否含有這個值,支持正則。
//?jest.test.js
test('是否包含?day?',?()?=>?{
??const?a?=?'happy?every?day';
??expect(a).toMatch('day');
})
toContain
toContain 匹配器,判斷當前數(shù)組中是否包含這個元素,Set 也可以使用。
//?jest.test.js
test('數(shù)組中是否包含?zoo?這個元素',?()?=>?{
??const?a?=?['zoo',?'ZooTeam',?'Zero'];
??expect(a).toContain('zoo');
})
toThrow
toThrow 匹配器,可以捕捉拋出的異常,參數(shù)為拋出的 error ,可以用來判斷是否為某個異常。
//?jest.test.js
const?error?=?()?=>?{
??throw?new?Error('error');
}
test('是否存在異常',?()?=>?{
??expect(error).toThrow();
})
????????以上就是?Jest
?中比較基礎的匹配器,可以結合?初始化?+?配置?+?基礎匹配器?進行書寫測試用例。
命令行操作
????????在運行?npm run test
?命令的時候,控制臺執(zhí)行測試用例成功或失敗后都會像下面的圖片一樣出現(xiàn)幾行提示,讓你按對應的鍵進行操作。
上面幾個命令行的意思如下:
1.?f
?只會跑測試未通過的用例,再次點擊 f 會取消當前模式。
? ? 我們使用一個失敗的測試用例做一下示范
? ? 按下?f
?后,Jest
?只會執(zhí)行剛才失敗的測試用例
2.?o
?只監(jiān)聽已改變的文件,如果存在多個測試文件,可以開啟,會與當前 git 倉庫中的提交進行比較,需要使用 git 來監(jiān)聽哪個文件修改了,也可以將 --watchAll 改為 --watch 只會運行修改的文件。
3. 根據(jù)測試用例文件的正則表達式,過濾需要執(zhí)行的測試用例文件,No tests found, exiting with code 0
?如果填寫不對會進行提示,并不會跑任何測試用例。
4. 根據(jù)測試用例描述的正則表達式,過濾需要執(zhí)行的測試用例。
5. 退出測試用例監(jiān)聽。
異步測試
????????在正常的業(yè)務開發(fā)中,項目中不只有同步代碼,還會有請求接口的異步代碼,異步代碼的測試與同步代碼有稍許不同,我們來看一下。
編寫一個接口請求
//?getData.js
export?const?getData?=?(fn)?=>?{
??axios.get('/getData').then((res)?=>?{
????fn(res.data);
??})
}
對異步請求進行測試
//?jest.test.js
//?異步調用回調函數(shù)需要添加?done?參數(shù),是一個函數(shù)
test('getData?返回結果為?{?success:?true?}',?(done)?=>?{
??//?此處代碼無效,因為測試用例不會等待請求結束后的回調,測試用例執(zhí)行完就直接結束了
??//?getData1((data)?=>?{
??//???expect(data).toEqual({
??//?????success:?true
??//???})
??//?})
??
??getData1((data)?=>?{
????expect(data).toEqual({
??????success:?true
????})
????//?需要在結束前調用?done?函數(shù),?Jest?會知道到?done?才會結束,才可以正確測試異步函數(shù)
????done();
??})
})
????????需要注意的是,如果傳入了形參?done
,但是沒有使用,這個測試用例就會處于一直執(zhí)行的狀態(tài),直到執(zhí)行超時。
還可以結合?promise
?進行使用
//?getData.js
export?const?getData2?=?()?=>?{
??return?axios.get('http://www.dell-lee.com/react/api/demo.json')
}
//?jest.test.js
test('getData?返回結果為?{?success:?true?}',?()?=>?{
??//?使用?promise?時需要?return,在?then?中使用?done?也可以
??return?getData2().then(res?=>?{
????expect(res.data).toEqual({
??????success:?true
????})
??})
})
//?測試請求是否?404
test('getData?返回結果為?404',?()?=>?{
??//?由于不觸發(fā)?catch?就不會走測試校驗,所以會成功,我們需要做一下限制
??//?這行代碼限制下面的代碼中必須要執(zhí)行一次?expect?方法,
// 如果非?404?就不會走下面的?expect,則測試不會通過
??expect.assertions(1);
??//?使用?promise?時需要?return
??//?如果只想測試?404?這樣寫是有問題的,需要配合?assertions?使用
??return?getData2().catch(err?=>?{
????expect(err.toString().indexOf('404')?>?-1).toBe(true)
??})
})
//?另一種寫法
test('getData?返回結果為?{?success:?true?}',?()?=>?{
??//?會返回很多數(shù)據(jù),其中包含?data?對象
??//?getData2().then((res)?=>?console.log(res))
??//?{
??//???status:?200,
??//???statusText:?'OK',
??//???headers:?{},
??//???......
??//???data:?{?success:?true?}
??//?}
??//?resolves?方法會將接口返回的字段全部獲取,再使用?toMatchObject?方法
// 進行匹配大對象中是否存在?data?對象
??return?expect(getData2()).resolves.toMatchObject({
????data:?{
??????success:?true
????}
??})
})
//?還可以使用?async/await
test('getData?返回結果為?{?success:?true?}',?async?()?=>?{
??await?expect(getData2()).resolves.toMatchObject({
????data:?{
??????success:?true
????}
??})
})
鉤子函數(shù)
????????鉤子函數(shù)可以當作一個測試用例的生命周期來看待,有?beforeAll
?、beforeEach
?、afterEach
?、afterAll
?。
以下是一些關于鉤子函數(shù)的概念和場景:
? ? ? ? ·beforeAll:在所有測試用例執(zhí)行前運行
? ? ? ? ·beforeEach:在每個測試用例執(zhí)行前執(zhí)行一次
? ? ? ? ·afterEach:在每個測試用例執(zhí)行后執(zhí)行一次
? ? ? ? ·afterAll:在所有測試用例結束后運行
????????有時候,需要測試一個類中的多個方法,這些方法可能會反復操作同一個對象上的屬性。如果使用同一個實例,就會相互干擾,導致測試用例無法通過。此時,需要使用不同的實例來進行測試。
Counter 類
//?Counter.js
class?Counter?{
??constructor()?{
????this.number?=?0;
??}
??add()?{
????this.number?+=?1;
??}
??minus()?{
????this.number?-=?1;
??}
}
export?default?Counter;
????????我們想要測試里面的?add
?和?minus
?方法是否正確,需要實例化一個對象進行測試。但是下面的測試用例使用的永遠都是同一個實例,第二個測試用例永遠都不會通過。因為執(zhí)行了第一個測試用例,第二個測試用例的值只能是 0。
//?jest.test.js
const?count?=?new?Counter();
//?使用下方兩種測試方法會互相影響,先加一后減一,結果永遠是?0
test('測試加法',?()?=>?{
??count.add();
??expect(count.number).toBe(1);
})
test('測試減法',?()?=>?{
??count.minus();
??expect(count.number).toBe(-1);
})
需要使用鉤子函數(shù),在每次執(zhí)行測試用例的時候,都讓他重新實例化一個對象
//?jest.test.js
let?count?=?null;
//?類似于生命周期
//?會在測試用例執(zhí)行前運行
beforeAll(()?=>?{
??console.log('beforeAll')
});
//?會在每個測試用例執(zhí)行前執(zhí)行一次,這樣就會解決上面互相影響的問題
beforeEach(()?=>?{
??console.log('beforeEach')
??count?=?new?Counter();
});
//?會在每個測試用例執(zhí)行后執(zhí)行一次
afterEach(()?=>?{
??console.log('afterEach')
});
//?會在所有測試用例結束后運行
afterAll(()?=>?{
??console.log('afterAll');
});
test('測試加法',?()?=>?{
??console.log('add')
??count.add();
??expect(count.number).toBe(1);
})
test('測試減法',?()?=>?{
??console.log('minus')
??count.minus();
??expect(count.number).toBe(-1);
})
分組方法 discribe
//?jest.test.js
let?count?=?null;
//?describe?方法,可以將測試用例進行分組,更加好維護同類型功能的測試用例
describe('count?測試',?()?=>?{
??beforeAll(()?=>?{
????console.log('beforeAll')
??});
??beforeEach(()?=>?{
????console.log('beforeEach')
????count?=?new?Counter();
??});
??afterEach(()?=>?{
????console.log('afterEach')
??});
??afterAll(()?=>?{
????console.log('afterAll');
??});
??
??//?將?add?類型進行分組
??describe('測試?add?類型用例',?()?=>?{
????//?在?describe?方法中,鉤子函數(shù)會按照層級嵌套進行執(zhí)行,
// 先執(zhí)行外部,再執(zhí)行內部,不同的?describe?互不干擾
????beforeEach(()?=>?{
??????console.log('beforeEach?add');
????});
????test('測試加法',?()?=>?{
??????console.log('add')
??????count.add();
??????expect(count.number).toBe(1);
????})
??})
??//?將?minus?類型進行分組
??describe('測試?minus?類型用例',?()?=>?{
????test('測試減法',?()?=>?{
??????console.log('minus')
??????count.minus();
??????expect(count.number).toBe(-1);
????})
??})
})
加上?describe
?方法的執(zhí)行效果如下圖。
Mock
????????在日常開發(fā)中,當前端開發(fā)差不多后,后端接口可能還沒有提供,這個時候我們就要用?Mock
?數(shù)據(jù)。而?Jest
?也有?Mock
?方法,用于模擬一些?JavaScript
?的函數(shù)等。
我們先來一個比較簡單的?mock.fn
//?mock.js
export?const?runFn?=?(fn)?=>?{
??fn(123);
}
//?mock.test.js
test('測試?runFn',?()?=>?{
??//?通過?jest?的?fn?方法創(chuàng)建一個模擬函數(shù),如果不傳參數(shù)會默認生成一個函數(shù)
??//?1.?通過?func.mock?獲取想要的值
??//?2.?可以自定義返回值
??//?3.?改變內部函數(shù)的實現(xiàn),模擬接口請求,不請求代碼中的接口
??const?func?=?jest.fn(?()?=>?456?);
??//?還可以使用?mockReturnValueOnce?方法進行控制輸出,
// 兩種方法都使用時會覆蓋?fn?方法中的返回值,支持鏈式調用
??//?將?Once?去掉與?fn?方法一樣,多次會返回相同的值
??func.mockReturnValueOnce('zoo')
??//?返回?this?方法?mockReturnThis
??func.mockReturnThis();
??//?還可以使用?mockImplementation?方法書寫函數(shù)內部,可以在函數(shù)內部寫邏輯,
// 與?jest.fn?方法的參數(shù)一樣,還可以填加?Once
??func.mockImplementation(()?=>?{
????return?'123';
??})
??//?執(zhí)行被測函數(shù)
??runFn(func);
??runFn(func);
??
??//?console.log(func.mock)
??//?因為被調用了兩次,所以長度都是?2
??//?{
??//???calls:?[?[123],?[123]?],??//?每次的調用情況,傳遞的參數(shù)是什么
??//???instances:?[?undefined,?undefined?],??//?每次調用的?this?指向,被調用了幾次
??//???invocationCallOrder:?[?1,?2?],??//?執(zhí)行順序,
// 可能會傳入同一個或多個方法中,需要記錄一下順序
??//???results:?[??//?mock?函數(shù)每次執(zhí)行后的返回值
??//?????{?type:?'return',?value:?456?},
??//?????{?type:?'return',?value:?456?}
??//???]
??//?}
??//?通過?toBeCalled?判斷函數(shù)是否被調用
??expect(func).toBeCalled();
??//?判斷當前函數(shù)調用了幾次?被調用了兩次
??expect(func.mock.calls.length).toBe(2);
??//?判斷參數(shù)是什么
??expect(func.mock.calls[0]).toEqual([123]);
??//?判斷每次調用的時候參數(shù)是什么
??expect(func).toBeCalledWith(123);
??//?判斷返回值
??expect(func.mock.results[0].value).toBe('zoo');
})
Mock 高階用法
????????如果需要通過修改請求的方式進行測試,而不使用測試框架,我們可能需要修改請求的代碼邏輯。但是,Jest
?提供了一種高級的?Mock
?方法。我們只需在項目根目錄下創(chuàng)建一個名為?__mocks__
?的文件夾,然后在其中自定義文件內容并導出,就可以使用自己定義的?Mock
?函數(shù)而不必修改請求代碼邏輯。
書寫測試用例文件,引入?__mocks__
?文件夾中的函數(shù)
//?mocker.test.js
//?使用?mock?方法引用?__mocks__?下創(chuàng)建的?mock.js
jest.mock("./mock");
//?執(zhí)行完上面的方法,會直接尋找?__mocks__?下的getData,而不是正常的請求文件
//?由于?mock?中沒有?getCode?方法,最好只?mock?異步函數(shù),同步函數(shù)直接測試即可
//?可以不必須創(chuàng)建?__mocks__?文件夾
import?{
??getData,
}?from?"./mock";
//?需要使用下面的?requireActual?方法來引用非?mock?文件夾下的?getCode
const?{?getData?}?=?jest.requireActual("./mock");
//?高階mock
//?此處直接使用?__mocks__?目錄下的?mock?文件中的函數(shù)
test("測試?getData",?()?=>?{
??return?getData().then((data)?=>?{
????expect(eval(data)).toEqual("123");
??});
});
Mock-timers
????????在特定的業(yè)務中,需要使用到定時器,測試的時候也是需要修改代碼來測試不同時間,最主要的一點是,我們需要等時間才能看到我們的執(zhí)行結果,Jest
?也有關于定時器的?Mock
函數(shù)。
//?mock.js
export?const?timer?=?(fn)?=>?{
??setTimeout(()?=>?{
????fn();
????setTimeout(()?=>?{
??????fn();
????},?3000)
??},?3000)
}
//?mock-timers.test.js
import?{?timer?}?from?'./mock';
//?使用?useFakeTimers?方法告知?Jest?在下面的測試用例,
// 如果用到了定時器異步函數(shù)的時候,都是用假的?timers?進行模擬
jest.useFakeTimers();
test('測試?timer',?()?=>?{
??const?fn?=?jest.fn();
??timer(fn);
??//?使用?runAllTimers?方法,讓定時器立即執(zhí)行,和?useFakeTimers?配合使用
??jest.runAllTimers();
??//?如果代碼中有多個定時器嵌套,只想測試最外層的定時器,則需要使用?runOnlyPendingTimers?方法
??//?這個方法會只執(zhí)行當前在隊列中的函數(shù),可以多次調用
??jest.runOnlyPendingTimers();
??jest.runOnlyPendingTimers();
??//?advanceTimersByTime?方法,可以快進時間
??//?因為?timer?中,三秒后只執(zhí)行了第一層,如果是六秒,則會執(zhí)行兩次?fn
??jest.advanceTimersByTime(3000);
})
snapshot 快照
????????到這里我們已經(jīng)可以測試一些代碼了,但是我們要如何捕捉執(zhí)行結果和當前做對比呢?這時候就要使用快照功能了。
//?snapshot.js
export?const?config1?=?()?=>?{
??return?{
????method:?'GET',
????url:?'/api',
????time:?new?Date()
??}
}
export?const?config2?=?()?=>?{
??return?{
????method:?'GET',
????url:?'/api',
????time:?new?Date().getTime()
??}
}
//?snapshot.test.js
import?{?config1,?config2?}?from?"./snapshot";
test('測試?config1?返回值',?()?=>?{
??//?但如果每次函數(shù)修改的時候,當前測試用例也要不斷地修改
??//?expect(config()).toEqual({
??//???method:?'GET',
??//???url:?'/api'
??//?});
??//?需要使用快照匹配?toMatchSnapshot?方法
??//?此方法會生成一個?__snapshots__?目錄,下面的文件中,
// 第一次執(zhí)行中?config?生成的結果會存到快照文件中
??//?快照會根據(jù)?test?方法中的描述生成一個映射關系
??//?修改后的?config?的執(zhí)行結果與快照中的結果不同時會報錯,需要更新快照
??//?如果?config?中有的值是每次運行都會變化的,那么每次快照都不會與當前執(zhí)行相同,
// 除非執(zhí)行后再更新快照
??//?需要將在?toMatchSnapshot?方法中傳遞一個參數(shù),設置一下?time?為任意格式的?Date?類型
??expect(config1()).toMatchSnapshot({
????time:?expect.any(Date)
??});
})
test('測試?config2?返回值',?()?=>?{
??expect(config2()).toMatchSnapshot({
????time:?expect.any(Number)
??});
})
行內快照生成
//?snapshot.test.js
//?需要安裝?prettier
test("測試?config2?返回值",?()?=>?{
??//?toMatchInlineSnapshot?方法,將執(zhí)行快照放到行內中,
// 會放到?toMatchInlineSnapshot?方法中
??expect(config2()).toMatchInlineSnapshot(
????{
??????time:?expect.any(Number)
????},
????`
????Object?{
??????"method":?"GET",
??????"time":?Any<Number>,
??????"url":?"/api",
????}
?? `
??);
});
對 dom 節(jié)點測試
????Jest
?內部自己模擬了一套?jsDom
?,可以在?node
?的環(huán)境下執(zhí)行需要瀏覽器環(huán)境?dom
的測試用例。
//?dom.js
import?$?from?'jquery';
const?addDiv?=?()?=>?{
??//?jQuery
??$('body').append('<div/>');
}
export?default?addDiv;
//?dom.test.js
import?addDiv?from?'./dom';
import?$?from?'jquery';
test('測試?addDiv',?()?=>?{
??addDiv();
??addDiv();
??console.log($('body').find('div').length);
??//?測試dom
??expect($('body').find('div').length).toBe(2);
??expect(document.getElementsByTagName('div').length).toBe(2);
})
VSCode 插件
????Jest Snippets
?用于快速生成?Jest
?代碼塊的工具。
????Jest
?能夠檢測當前文件夾中的測試用例并自動運行測試,還支持可視化操作,更新、執(zhí)行以及單個執(zhí)行等功能,非常方便!
常用配置解讀
module.exports?=?{
??//?檢測從哪個目錄開始,rootDir?代表根目錄
??roots:?["<rootDir>/src"],
??//?代碼測試覆蓋率通過分析那些文件生成的,!代表不要分析
??collectCoverageFrom:?[
????//?src?下所有?js?jsx?ts?tsx?后綴的文件
????"src/**/*.{js,jsx,ts,tsx}",
????//?src?下所有?.d.ts?后綴的文件
????"!src/**/*.d.ts"
??],
??//?運行測試之前,我們額外需要準備什么
??setupFiles:?["react-app-polyfill/jsdom"],
??//?當測試環(huán)境建立好后,需要做其他事情時可以引入對應的文件
??setupFilesAfterEnv:?["<rootDir>/src/setupTests.js"],
??//?哪些文件會被認為測試文件
??testMatch:?[
????//?src?下的所有?__tests__?文件夾中的所有的?js?jsx?ts?tsx?后綴的文件都會被認為是測試文件
????"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
????//?scr?下的所有一?.test/spec.js/jsx/ts/tsx?后綴的文件都會被認為是測試文件
????"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}",
??],
??//?測試運行的環(huán)境,會模擬?dom
??testEnvironment:?"jsdom",
??//?測試文件中引用一下后綴結尾的文件會使用對應的處理方式
??transform:?{
????//?如果引用的是?js?jsx?mjs?cjs?ts?tsx?后綴的文件會使用?/config/jest/babelTransform.js?文件進行處理
????"^.+\\.(js|jsx|mjs|cjs|ts|tsx)$":?"<rootDir>/config/jest/babelTransform.js",
????//?如果引用的是?css?后綴的文件,會使用?/config/jest/cssTransform.js?文件處理
????"^.+\\.css$":?"<rootDir>/config/jest/cssTransform.js",
????//?不是以?js?jsx?mjs?cjs?ts?tsx?css?json?這些為后綴的文件會使用?/config/jest/fileTransform.js?文件進行處理
????"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)":
??????"<rootDir>/config/jest/fileTransform.js",
??},
??//?忽略?transform?配置轉化的文件
??transformIgnorePatterns:?[
????//?node_modules?目錄下的?js?jsx?mjs?cjs?ts?tsx?后綴的文件都不需要轉化
????"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",
????//?.module.css/sass/scss?后綴的文件都不需要轉化
????"^.+\\.module\\.(css|sass|scss)$",
??],
??//?自動化測試時,應用的模塊應該從哪里尋找,默認是在?node_modules
??modulePaths:?[],
??//?模塊名字使用哪種工具進行映射
??moduleNameMapper:?{
????//?針對于?native?移動端
????//?"^react-native$":?"react-native-web",
????//?將?.module.css/sass/scss?模塊使用?identity-obj-proxy?工具進行轉化
????"^.+\\.module\\.(css|sass|scss)$":?"identity-obj-proxy",
??},
??//?引入模塊時,進行自動查找模塊類型,逐個匹配
??moduleFileExtensions:?[
????"web.js",
????"js",
????"web.ts",
????"ts",
????"web.tsx",
????"tsx",
????"json",
????"web.jsx",
????"jsx",
????"node",
??],
??//?監(jiān)聽插件
??watchPlugins:?[
????"jest-watch-typeahead/filename",
????"jest-watch-typeahead/testname",
??],
??//?重置?mock
??resetMocks:?true,
};
小結
????????在實際業(yè)務應用中,我們建議對可復用的組件、工具函數(shù)、工具類等一些無副作用,可預知結果的代碼來進行單元測試。在前期開發(fā)過程中的投入會大于沒有單元測試的投入,因為要寫一些測試用例,還要執(zhí)行測試用例,優(yōu)化代碼等。但是在長久迭代中,這種方法會比沒有進行單元測試的模塊更加穩(wěn)定。
代碼地址
-
前置?
demo
?:https://github.com/Jadony/Jest-demo -
Jest
?簡單配置:https://github.com/Jadony/jest-config -
Jest
?匹配器:https://github.com/Jadony/jest-matchers -
異步代碼測試:https://github.com/Jadony/jest-async
-
Jest
?鉤子函數(shù):https://github.com/Jadony/jest-hook -
Jest
?的?mock
?函數(shù):https://github.com/Jadony/jest-mock -
Jest
?的快照:https://github.com/Jadony/jest-snapshot -
Jest
?對?Dom
?節(jié)點的測試:https://github.com/Jadony/jest-dom
參考文檔
-
《前端要學的測試課 從Jest入門到TDD/BDD雙實戰(zhàn)》(https://coding.imooc.com/class/chapter/372.html#Anchor)文章來源:http://www.zghlxwxcb.cn/news/detail-466652.html
????????如果想學習更多內容,請移步至 Jest(https://jestjs.io/zh-Hans/docs/getting-started) 官方文檔。文章來源地址http://www.zghlxwxcb.cn/news/detail-466652.html
到了這里,關于前端自動化測試 —— Jest 測試框架應用的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!