前言:前端領(lǐng)域的自動(dòng)化測(cè)試
一直以來(lái)對(duì)于前端同學(xué)來(lái)說(shuō),自動(dòng)化測(cè)試都是一個(gè)比較特殊的命題。一方面,大家其實(shí)都知道自動(dòng)化測(cè)試的好處,做了什么改動(dòng)只要跑一遍測(cè)試用例就知道有沒(méi)有改掛了之前的邏輯,進(jìn)行修改時(shí)也更有底氣。而另一方面,前端本身就具有特殊性,活動(dòng)頁(yè)從需求評(píng)審到正式上線可能在一周內(nèi)就完成了,這種迭代速度還寫(xiě)測(cè)試用例就是折磨自己。
但實(shí)際上,自動(dòng)化測(cè)試在前端工程中也是相當(dāng)重要的一部分。即使是快速迭代的活動(dòng)頁(yè)面,也會(huì)有通用的工具函數(shù)與 SDK,對(duì)這一部分的代碼進(jìn)行測(cè)試用例的完善是有必要且意義重大的,而對(duì)于某些流量巨大且長(zhǎng)期存在的頁(yè)面,我們甚至需要進(jìn)行多種測(cè)試場(chǎng)景的保障。
然而由于這兩種情況的存在,很多前端同學(xué)其實(shí)都對(duì)自動(dòng)化測(cè)試的認(rèn)知相當(dāng)空白,它有哪些分類?有哪些推薦的實(shí)踐?有哪些框架與方案?而這篇文章的目的就是進(jìn)行一個(gè)基礎(chǔ)的掃盲,至少完成閱讀以后你會(huì)知道如何為項(xiàng)目編寫(xiě)測(cè)試用例,以及應(yīng)該編寫(xiě)哪些場(chǎng)景的測(cè)試用例。
單元測(cè)試與集成測(cè)試
單元測(cè)試(Unit Testing)正如其名,其中的測(cè)試用例應(yīng)當(dāng)是針對(duì)代碼中的各個(gè)單元的,如前端代碼中,每一個(gè)工具方法都可以被作為一個(gè)單元,對(duì)應(yīng)一個(gè)獨(dú)立的測(cè)試用例。但這么說(shuō)并不意味著你要寫(xiě)出非常細(xì)粒度的代碼——這不是沒(méi)事折磨自己?jiǎn)幔课彝ǔJ褂谩肮δ軉卧钡姆绞絹?lái)確定粒度,比如生產(chǎn)薯?xiàng)l的流水線上,清洗-削皮-切片-包裝就是四個(gè)完全獨(dú)立的功能單元。
你可能會(huì)感到疑惑,這四個(gè)功能單元明明存在依賴關(guān)系,為何說(shuō)是完全獨(dú)立的?這是因?yàn)樵趩卧獪y(cè)試時(shí),非常重要的一個(gè)步驟就是對(duì)當(dāng)前測(cè)試單元的外部依賴進(jìn)行模擬,比如我在測(cè)試削皮功能時(shí),會(huì)直接給到“已經(jīng)清洗完畢的土豆”,然后檢查“削皮后的土豆”,而不會(huì)真的去調(diào)用前后的功能單元。
常見(jiàn)的模擬操作可以分為 Fake、Stub、Mock、Spy 這么幾種,我們?cè)谙挛臅?huì)有更詳細(xì)的介紹。
一種常見(jiàn)的情況是工具方法中會(huì)基于外部依賴的表現(xiàn)執(zhí)行不同分支的代碼(if/else,try/catch,resolve/reject 等)。這種時(shí)候,我們需要做的就是通過(guò)修改外部依賴的表現(xiàn),來(lái)檢查工具方法內(nèi)部各個(gè)代碼分支的執(zhí)行情況。比如,在 fetch 成功返回時(shí)應(yīng)當(dāng)調(diào)用 processData 方法,在 fetch 失敗時(shí)應(yīng)當(dāng)調(diào)用 reportError 方法,此時(shí)你就可以篡改掉 fetch 的實(shí)現(xiàn),然后檢查 processData 、reportError 方法是否被調(diào)用(注意,這兩個(gè)方法也需要被模擬(Stub / Spy) ,然后才能檢查它們的調(diào)用情況)。
當(dāng)然,完全模擬所有外部依賴是最理想的情況,在很多時(shí)候一個(gè)工具方法可能具有許多外部依賴,此時(shí)你可以省略掉其中能確定無(wú)副作用(如 logger 這樣的純函數(shù)),或者是與核心邏輯無(wú)關(guān)的部分。
我們知道,測(cè)試用例也可以反過(guò)來(lái)對(duì)代碼產(chǎn)生檢查作用,而在單元測(cè)試階段這種作用基本是最明顯的,比如你可以很容易發(fā)現(xiàn)某一處功能單元設(shè)計(jì)得過(guò)于耦合,或是某一外部依賴將導(dǎo)致代碼進(jìn)入錯(cuò)誤分支等情況。
目前推薦的單元測(cè)試方案主要有這么幾種,Jest、Mocha、Sinon、Jasime 以及 AVA,它們之間各有優(yōu)劣,這里不做比較。但需要注意的是,一套完整的,能夠滿足實(shí)際需求的單元測(cè)試方案,通常意味著需要包括這么幾個(gè)功能:
如果你此前并沒(méi)有對(duì)這些單元測(cè)試方案非常熟悉,那我推薦你了解一下 Vitest ,來(lái)自 antfu 的作品,特色是快(畢竟基于 Vite)以及對(duì) TypeScript、ES Module 的良好支持,我目前在工作中的單元測(cè)試也已經(jīng)全部遷移到 Vitest,同時(shí) Vitest 還自帶了 UI 界面,讓你可以更享受編寫(xiě)測(cè)試并看著它們一個(gè)個(gè)通過(guò)的過(guò)程。
測(cè)試覆蓋率報(bào)告,這一功能常見(jiàn)的方式是通過(guò) istanbul (1.0版本,2.0 更名為 nyc)或 c8 來(lái)進(jìn)行實(shí)現(xiàn),其原理包括代碼插樁與使用 V8 引擎內(nèi)置功能兩種,這里不再贅述。另外一個(gè)常見(jiàn)的場(chǎng)景是輸出其他語(yǔ)言格式的覆蓋率報(bào)告(如 JUnit),社區(qū)也通過(guò) Reporters 的機(jī)制為這些測(cè)試框架做了支持。
模擬功能(Stub 、Fake Timers 等),包括對(duì)一個(gè)對(duì)象的 Spy,一個(gè)函數(shù)的 Stub,對(duì)一個(gè)模塊的 Mock,都屬于模擬的范疇。
用例收集,編寫(xiě)測(cè)試用例時(shí)我們同樣需要基于功能單元區(qū)分,常見(jiàn)的方式就是 describe 收集一個(gè)功能單元,內(nèi)部又使用 it / test 來(lái)進(jìn)行功能單元各個(gè)邏輯分支的驗(yàn)證。如:
describe('Utils.Reporter',()=>{
it('shouldreporterrorwhenxxx',()=>{})
it('shouldshowwarningswhenxxx',()=>{})
})
斷言,Jest 提供了注入到全局的 expect 風(fēng)格斷言(expect(1+1).toBe(2)),而 Sinon 提供的則是類似 NodeJs asserts 模塊風(fēng)格的斷言(``sinon.assert.pass(1 + 1 === 2)`),而 Mocha 則不綁定斷言庫(kù),你可以使用 asserts 模塊或者 Chai 來(lái)進(jìn)行斷言。另外,斷言又包括了幾種不同的風(fēng)格,我們同樣在下文講解。
如果說(shuō)單元測(cè)試是為了測(cè)試單個(gè)功能單元,那么集成測(cè)試(Integration Testing)很明顯就是為了測(cè)試多個(gè)功能單元的協(xié)作。但需要注意的是,多個(gè)功能單元協(xié)作并不意味著對(duì)整個(gè)系統(tǒng)(流水線)進(jìn)行完整的功能測(cè)試,通常我們還會(huì)將幾個(gè)功能單元分散開(kāi)進(jìn)行組合,成為系統(tǒng)的某一部分,比如清洗-削皮作為預(yù)處理功能,需要確定一籮筐土豆能否正確地變成干凈的去皮土豆,切片-包裝作為核心功能,需要確定去皮土豆能變成冷凍薯?xiàng)l。
而要進(jìn)行集成測(cè)試的編寫(xiě),其實(shí)我們?nèi)匀恢恍枰褂脝卧獪y(cè)試方案即可,因?yàn)楸举|(zhì)上集成測(cè)試就是同時(shí)對(duì)多個(gè)功能單元進(jìn)行測(cè)試,我們驗(yàn)證的范疇也隨之?dāng)U大了而已。
而關(guān)于集成測(cè)試的維度拆分則并沒(méi)有準(zhǔn)確的界限,你可以像上面那樣將預(yù)處理功能作為一個(gè)系統(tǒng)部分,也可以將整個(gè)流水線作為一個(gè)系統(tǒng)部分(還有供應(yīng)鏈部分、烹飪部分與服務(wù)部分),按照你的實(shí)際業(yè)務(wù)場(chǎng)景就行。
Mock、Fake、Stub
很多時(shí)候測(cè)試用例的運(yùn)行時(shí)是受限的,比如我們并不希望真的發(fā)起網(wǎng)絡(luò)請(qǐng)求,或者是和數(shù)據(jù)庫(kù)交互,以及 DOM API 的操作等。這個(gè)時(shí)候我們會(huì)使用一系列模擬手段,來(lái)特定地模擬一個(gè)可交互的對(duì)象,并通過(guò)修改它的行為來(lái)檢查預(yù)期的處理邏輯是否執(zhí)行。
這個(gè)模擬行為通常被直接稱為 Mock,但實(shí)際上,由于模擬的對(duì)象類型以及注入的模擬邏輯,更準(zhǔn)確的描述是將這些行為劃分為三大類。首先是最常用的 Stub ,假設(shè)我們?cè)跒?UserService 編寫(xiě)單元測(cè)試,其內(nèi)部注入的 PrismaService 負(fù)責(zé)數(shù)據(jù)庫(kù)的讀寫(xiě),我們可以使用一個(gè) PrismaServiceStub 替換掉實(shí)際的服務(wù),并且在其內(nèi)部提供對(duì)應(yīng) PrismaService.user.findUnique 這樣的方法,然后在我們調(diào)用 UserService.queryUser 時(shí),就可以檢查 PrismaServiceStub 上對(duì)應(yīng)的方法是否被預(yù)期的入?yún)⒄{(diào)用,而其出參是否被預(yù)期地處理后返回。Spy 也可以認(rèn)為是 Stub 的一種,但它更強(qiáng)調(diào)“是否按照預(yù)期調(diào)用”這個(gè)過(guò)程,我們甚至可以僅僅監(jiān)聽(tīng)一個(gè)對(duì)象而無(wú)需提供模擬實(shí)現(xiàn)(如 console 這樣的 API)。
而如果我們不希望替換掉 PrismaService,而是希望它真的去進(jìn)行數(shù)據(jù)讀寫(xiě),但不是對(duì)真實(shí)的數(shù)據(jù)庫(kù),就可以提供一個(gè) Fake 的數(shù)據(jù)庫(kù)——比如一個(gè)對(duì)象,這樣對(duì)數(shù)據(jù)庫(kù)的讀寫(xiě)就變成了對(duì)內(nèi)存對(duì)象的讀寫(xiě),變得更加快捷和穩(wěn)定,這就是 Fake。另外一個(gè)常見(jiàn)的 Fake 場(chǎng)景就是定時(shí)器,常見(jiàn)的單元測(cè)試框架都提供了 Fake Timers 的功能支持。
而 Mock 其實(shí)和 Stub 也非常類似,但 Mock 更像是其中“預(yù)期的入?yún)ⅰ保⒉魂P(guān)注返回值,我個(gè)人理解通常項(xiàng)目中 fixtures 文件夾下的各種對(duì)象和 JSON 就是典型的 Mock 。
當(dāng)然,Mock、Stub、Spy 三者還是非常相似的,我們也并不是必須搞清楚其中的差異,因?yàn)樗鼈兊谋举|(zhì)都是模擬罷了。
斷言:expect、assert、should
我們常見(jiàn)的斷言包括 expect 與 assert 形式,NodeJs 提供了原生的 asserts 模塊讓你來(lái)編寫(xiě)一些簡(jiǎn)單的斷言,你可以在實(shí)際代碼中也使用斷言來(lái)確保邏輯正確運(yùn)行,而 expect 形式則通常只見(jiàn)于測(cè)試用例中。如檢查一個(gè)函數(shù)的調(diào)用和比較兩個(gè)對(duì)象,兩種風(fēng)格分別是這樣的:
expect(mockFn).toBeCalledWith(
"linbudu"
);
assert.pass(mockFn.calls[
0
].arg===
"linbudu"
);
expect(obj1).toEqual(obj2);
assert.equal(obj1,obj2);
通常我個(gè)人更喜歡命令式風(fēng)格明顯的 expect 斷言,而除了這兩種風(fēng)格以外,其實(shí)還有一種 should 形式的鏈?zhǔn)斤L(fēng)格斷言,它寫(xiě)起來(lái)是這樣的:
mockFn.should.be.called();
obj1.should.equal(obj2);
值得一提的是在 Chai 這個(gè)斷言庫(kù)中對(duì)以上三種斷言風(fēng)格都進(jìn)行了支持,如果你有興趣,不妨都試一試。
前端頁(yè)面中的組件測(cè)試與 E2E 測(cè)試
單元測(cè)試和集成測(cè)試是前后端應(yīng)用中通用的概念,而完成了對(duì)基礎(chǔ)功能單元的測(cè)試以后,我們需要更進(jìn)一步,關(guān)注領(lǐng)域中特定的功能,比如從前端視角來(lái)看一個(gè)組件的 UI 與功能,從后端視角來(lái)看一個(gè)接口面對(duì)千奇百怪入?yún)⒌捻憫?yīng)。
在當(dāng)今的前端項(xiàng)目中,組件化應(yīng)該是最明顯的一個(gè)趨勢(shì),那么進(jìn)行組件維度的測(cè)試也自然是相當(dāng)有必要的。以 React 組件為例,我們可以模擬這個(gè)組件的入?yún)?,并觀察其實(shí)際渲染的 UI 組件是否正確,以及使用快照的方式,來(lái)檢查組件的實(shí)際渲染是否一致。
目前使用的組件測(cè)試方案通常是和框架綁定的,如 React 下的 @testing-library/react 和 Enzyme,Vue 下的 @vue/test-utils,Svelte 下的 @testing-library/svelte,這是因?yàn)楸举|(zhì)上我們是在孤立地渲染這個(gè)組件,并模擬框架行為來(lái)驗(yàn)證其表現(xiàn)。
在組件測(cè)試方案中,我更推薦 @testing-library/react (還包括 @testing-library/react-hooks),Enzyme 的 API 要更加復(fù)雜,同時(shí)其目前應(yīng)該已經(jīng)不再維護(hù)(或是維護(hù)力度堪憂)。使用其編寫(xiě)的測(cè)試用例是這樣的:
import
*
as
React
from
'react'
function
HiddenMessage
({children}){
const
[showMessage,setShowMessage]=React.useState(
false
)
return
(
<
div
>
<
label
htmlFor
=
"toggle"
>ShowMessage
label
>
<
input
id
=
"toggle"
type
=
"checkbox"
onChange
=
{e
=>setShowMessage(e.target.checked)}
checked={showMessage}
/>
{showMessage?children:null}
div
>
)
}
export
default
HiddenMessage
import
*
as
React
from
'react'
import
{render,fireEvent,screen}
from
'@testing-library/react'
import
HiddenMessage
from
'../hidden-message'
test(
'showsthechildrenwhenthecheckboxischecked'
,()=>{
const
testMessage=
'TestMessage'
//將組件模擬渲染出來(lái)
render(<
HiddenMessage
>{testMessage}
HiddenMessage
>)
//基于模糊查詢來(lái)驗(yàn)證DOM元素的存在
expect(screen.queryByText(testMessage)).toBeNull()
//同樣基于模糊查詢來(lái)觸發(fā)事件
fireEvent.click(screen.getByLabelText(
/show/i
))
//驗(yàn)證結(jié)果是否符合預(yù)期
expect(screen.getByText(testMessage)).toBeInTheDocument()
})
單元測(cè)試、集成測(cè)試、組件測(cè)試,看起來(lái)我們已經(jīng)非常完美地使用自動(dòng)化測(cè)試從不同場(chǎng)景與不同維度進(jìn)行了功能的驗(yàn)證,但實(shí)際上,我們還少了一個(gè)非常重要的維度——用戶視角。在程序最終交付驗(yàn)收時(shí),我們可愛(ài)的測(cè)試同學(xué)會(huì)來(lái)把各個(gè)功能和鏈路都檢查一遍,而即使你已經(jīng)寫(xiě)了巨量的測(cè)試用例,還是有可能會(huì)被發(fā)現(xiàn)大量的問(wèn)題,這就是因?yàn)橐暯遣煌?。作為程序?a target="_blank">開(kāi)發(fā)者,你清楚地了解程序的控制流走向,也對(duì)每一個(gè)分支了然于胸,所以在編寫(xiě)測(cè)試用例時(shí)你其實(shí)更像是上帝視角。
要從用戶的視角出發(fā),實(shí)際上我們只需要屏蔽對(duì)程序內(nèi)部的所有感知,而只是去使用這個(gè)程序即可。這樣的測(cè)試被稱為端到端測(cè)試(End-to-End Testing,E2E),它不再關(guān)注內(nèi)部功能單元的細(xì)節(jié),而是完全從外部還原一個(gè)真實(shí)的用戶視角,如前端應(yīng)用中,用戶登錄-搜索商品-加入購(gòu)物車-編輯商品-結(jié)算商品的一系列交互,誰(shuí)管你的登錄背后隱藏了多少權(quán)限分級(jí),商品貨架分級(jí)設(shè)計(jì)得多么精細(xì),只要這個(gè)流程無(wú)法順利走通,那你的系統(tǒng)就是有問(wèn)題的。
而既然 E2E 測(cè)試是在模擬用戶行為,那么其實(shí)我們所需要做的就是使用用戶的環(huán)境來(lái)運(yùn)行系統(tǒng)罷了。如對(duì)于前端頁(yè)面,其實(shí)就是瀏覽器(更準(zhǔn)確地說(shuō)是瀏覽器內(nèi)核),而對(duì)于后端服務(wù)則是客戶端。
以 Cypress 的功能為例,來(lái)看看我們是如何模擬用戶行為的:
在前端領(lǐng)域中編寫(xiě) E2E 測(cè)試,常見(jiàn)的 E2E 測(cè)試框架主要包括 Puppeteer、Cypress、Playwright、Selenium 這么幾種。它們之間各有優(yōu)劣,適用場(chǎng)景也有所不同,我們會(huì)在下面進(jìn)行比較。
與其他測(cè)試場(chǎng)景的重要不同之一,就是 E2E 測(cè)試是可以由測(cè)試同學(xué)來(lái)編寫(xiě)的(如支持 Python 和 Java 的 Selenium),在產(chǎn)品進(jìn)行迭代的同時(shí),測(cè)試同學(xué)會(huì)按照功能點(diǎn)變化對(duì)應(yīng)地完善測(cè)試用例,同時(shí)確保以往所有功能的測(cè)試用例不受影響。
Puppeteer、Cypress、Playwright 的取舍
前端 E2E 測(cè)試目前常用的包括 Puppeteer、Cypress、Playwright 這么幾款,這就可能讓你感到選擇困難,到底應(yīng)該選哪個(gè)?萬(wàn)一選了一個(gè),寫(xiě)著寫(xiě)著發(fā)現(xiàn)不符合需求了,咋辦?這一部分我們就來(lái)簡(jiǎn)單介紹一下它們。
先上結(jié)論:非常簡(jiǎn)單的場(chǎng)景使用 Puppeteer(需要搭配 Jest-Puppeteer),PC 應(yīng)用使用 Cypress,移動(dòng)端應(yīng)用使用 Playwright。
接著我們?cè)賮?lái)一個(gè)個(gè)解釋。首先是 Puppeteer,認(rèn)真地說(shuō),它就不應(yīng)該用來(lái)做 E2E 測(cè)試,因?yàn)槿思艺娴木椭皇且粋€(gè)無(wú)頭瀏覽器,你要用它來(lái)寫(xiě)寫(xiě)爬蟲(chóng)之類的倒還好,強(qiáng)行霸王硬上弓要人家給你干 E2E,一方面是只支持 Chrome + Chromium 內(nèi)核,另一方面人家不帶斷言庫(kù),你還得帶一個(gè) Jest-Puppeteer 一起。但如果是真的非常非常簡(jiǎn)單的場(chǎng)景,你還是可以用 Puppeteer ,加上 NodeJs 的基礎(chǔ)斷言庫(kù),通過(guò)自動(dòng)化方式確定一些頁(yè)面功能還是沒(méi)問(wèn)題的。
然后是 Cypress ,其場(chǎng)景從人家的 Slogan 其實(shí)也能感覺(jué)出來(lái):Fast, easy and reliable testing for anything that runs in a browser,注意 in a browser,其實(shí)人家就是提供了無(wú)頭瀏覽器,斷言,GUI,以及 Web 下的各種 API ,然后你就可以完全模擬使用瀏覽器進(jìn)行的一切行為了。同時(shí) Cypress 也通過(guò)代碼插樁的方式支持了覆蓋率報(bào)告相關(guān)的能力。需要注意的是,Cypress 只支持瀏覽器維度的配置,如 chrome(也支持chromium)、edge、firefox。
因此,如果你更側(cè)重于檢查應(yīng)用在移動(dòng)端的表現(xiàn),那其實(shí)應(yīng)該使用 Playwright 。為什么?Playwright 支持同時(shí)運(yùn)行多個(gè) project,這些 project 可以被配置為使用瀏覽器內(nèi)核(檢驗(yàn) PC、桌面端場(chǎng)景),也可以被配置為使用內(nèi)置的 devices 預(yù)設(shè),來(lái)檢驗(yàn)其在移動(dòng)端的表現(xiàn),這些預(yù)設(shè)包括了視口大小、默認(rèn)瀏覽器內(nèi)核(chromium,webkit,safari 等)等等,參考官網(wǎng)的示例配置:
//playwright.config.ts
import
{
type
PlaywrightTestConfig,devices}
from
'@playwright/test'
;
const
config:PlaywrightTestConfig={
projects:[
{
name:
'DesktopChromium'
,
use:{
browserName:
'chromium'
,
viewport:{width:
1280
,height:
720
},
},
},
{
name:
'DesktopSafari'
,
use:{
browserName:
'webkit'
,
viewport:{width:
1280
,height:
720
},
}
},
{
name:
'DesktopFirefox'
,
use:{
browserName:
'firefox'
,
viewport:{width:
1280
,height:
720
},
}
},
{
name:
'MobileChrome'
,
use:devices[
'Pixel5'
],
},
{
name:
'MobileSafari'
,
use:devices[
'iPhone12'
],
},
],
};
export
default
config;
在我所在的團(tuán)隊(duì),目前也正在基于 Playwright 建立 E2E 測(cè)試用例,來(lái)更方便快捷地保障核心應(yīng)用的頁(yè)面功能。
最后,如果你并不是在開(kāi)發(fā)一個(gè)前端應(yīng)用,而是在開(kāi)發(fā)一個(gè) UI 組件庫(kù),那你可以使用 StoryBook 提供的測(cè)試能力(基于 Jest 與 Playwright),這樣一來(lái)你既能夠基于 StoryBook 獲得組件的可視化文檔說(shuō)明,也可以獲得自動(dòng)生成的 E2E 測(cè)試用例:
后端服務(wù)中的 E2E 測(cè)試與壓力測(cè)試
而對(duì)于后端服務(wù)中的測(cè)試,由于我暫時(shí)還沒(méi)有比較深入地實(shí)踐,這里就只簡(jiǎn)單介紹下 Node API 中的 E2E 測(cè)試、壓力測(cè)試。
上面我們已經(jīng)提到,對(duì)于后端服務(wù)來(lái)說(shuō)其實(shí)用戶就是各個(gè)客戶端,而我們也只需要模擬客戶端,向 API 發(fā)起請(qǐng)求,模擬登錄態(tài)信息和各種參數(shù),然后查看最終返回的結(jié)果是否符合預(yù)期即可。在這個(gè)過(guò)程中,API 由哪個(gè) Controller 承接,調(diào)用了哪些 Service,走過(guò)了哪些 Middleware ,我們都不應(yīng)該也無(wú)需關(guān)心。而假裝自己是客戶端的方式就簡(jiǎn)單多了,常見(jiàn)的方式是使用 supertest 。另外,通常后端服務(wù)的 E2E 測(cè)試也應(yīng)該是盡量模擬完整的交互過(guò)程:上傳商品-編輯商品-上架商品-下架商品-...,只不過(guò)這個(gè)過(guò)程并不像在前端那樣直觀。
另外后端服務(wù)中的 E2E 測(cè)試如何 Mock 也有不同的情況,如果希望盡可能模擬用戶,可以使用專用的測(cè)試環(huán)境數(shù)據(jù)庫(kù),但這樣測(cè)試的執(zhí)行就不完全穩(wěn)定。如果希望從簡(jiǎn),那么可以像單元測(cè)試與集成測(cè)試中那樣模擬掉外部依賴。另外,部分 NodeJs 框架也直接提供了原生的測(cè)試支持,如 @nestjs/testing,@midwayjs/mock 等等。
另外一個(gè)后端服務(wù)特殊的測(cè)試場(chǎng)景則是壓力測(cè)試,在某些時(shí)候也可以被等價(jià)于性能測(cè)試,從某些方面它其實(shí)也是在模擬用戶,只不過(guò)不是模擬一個(gè)用戶的交互行為,而是模擬較大量級(jí)的用戶訪問(wèn),以此來(lái)測(cè)試服務(wù)的性能。本質(zhì)上壓力測(cè)試并不是在測(cè)試 API 的邏輯,而是承載 API 的服務(wù)器性能與負(fù)載均衡相關(guān)邏輯。進(jìn)行壓力測(cè)試可以很簡(jiǎn)單地使用腳本開(kāi)多線程并發(fā)請(qǐng)求,也可以使用 Apache Bench、Webbench、wrk(wrk2)測(cè)試工具,或者 npm 社區(qū)也有 autocannon 這樣的實(shí)現(xiàn)。
在壓力測(cè)試下,我們主要關(guān)注這么幾個(gè)指標(biāo):
每秒請(qǐng)求數(shù) RPS,Request Per Second,更常見(jiàn)的稱呼是每秒查詢數(shù) QPS,Query Per Second,它代表了到達(dá)服務(wù)器的請(qǐng)求數(shù)量。
并發(fā)用戶數(shù) CL,Concurrency Level,不同于 RPS,并發(fā)數(shù)代表了當(dāng)前仍未完結(jié)的等待處理的請(qǐng)求。舉例來(lái)說(shuō),假設(shè)某個(gè)神奇 API 的請(qǐng)求處理速度非常快,每個(gè)請(qǐng)求的處理時(shí)間無(wú)限趨近于 0 ,那么即使其 RPS 可能達(dá)到一百萬(wàn),并發(fā)數(shù)卻也非常低(趨近于0)——因?yàn)樗幚淼膶?shí)在是太快了,幾乎不需要同時(shí)處理兩個(gè)請(qǐng)求。
每秒事務(wù)數(shù) TPS,Transactions Per Second,TPS 有點(diǎn)類似于 QPS,但它所關(guān)注的事務(wù)其實(shí)是比請(qǐng)求-響應(yīng)過(guò)程更具象的過(guò)程,舉例來(lái)說(shuō),訪問(wèn) server/index.html ,實(shí)際上還訪問(wèn)了 server/index.css 與 server/index.js 文件,那么這個(gè)過(guò)程實(shí)際上只會(huì)記為一次事務(wù),但會(huì)記為三次查詢。
響應(yīng)時(shí)間 RT,Response Time,一個(gè)請(qǐng)求從進(jìn)入到帶走響應(yīng)的耗時(shí),這個(gè)耗時(shí)包括了等待時(shí)間-處理時(shí)間-IO讀寫(xiě)時(shí)間-響應(yīng)到達(dá)時(shí)間。
除了這些指標(biāo)以外,我們還會(huì)關(guān)注服務(wù)器當(dāng)前的性能指標(biāo),如內(nèi)存與 CPU 占用率,駐留集(RSS,當(dāng)前進(jìn)程獲得分配的物理內(nèi)存,包括堆、棧與執(zhí)行代碼段等),你也可以使用 NodeJs 提供的 --prof --prof-process 等啟動(dòng)參數(shù),或使用 heapdump 提供的內(nèi)存快照打印功能來(lái)幫助分析 Node API 的性能。
尾聲
除了以上介紹的這些自動(dòng)化測(cè)試分類,其實(shí)還有著前端頁(yè)面的性能測(cè)試(如基于 LightHouse、Performance API),主要關(guān)注各種“首次”的指標(biāo),如首屏繪制、可交互時(shí)間、最大內(nèi)容繪制等等,基于 axe-core 的可訪問(wèn)性測(cè)試(Accessibility Testing),關(guān)注網(wǎng)頁(yè)的可訪問(wèn)性,以及一些相對(duì)少見(jiàn)的場(chǎng)景,如基于 Needle 的 CSS 測(cè)試、基于 Coffee 的命令行應(yīng)用測(cè)試,以及混沌工程理念中的混沌測(cè)試等,這些概念要么在社區(qū)里已經(jīng)存在大量的高質(zhì)量介紹文章,要么我并沒(méi)有深入了解過(guò),在這里就不贅述了。
另外,想要進(jìn)一步地保障頁(yè)面的功能穩(wěn)定性,監(jiān)控平臺(tái)(白屏,JS Error,404)這一類的存在也是相當(dāng)有意義的,但這一部分功能已經(jīng)存在太多方案,社區(qū)的 Sentry,以及各大廠內(nèi)部自己建設(shè)的平臺(tái)等等,這里就不再贅述。
這篇不長(zhǎng)也不短的小作文里,我們基本上把前端開(kāi)發(fā)者會(huì)接觸到的自動(dòng)化測(cè)試種類都了解了一遍,包括它們的使用場(chǎng)景,實(shí)踐方式,以及可選的庫(kù)/框架。在完成全文閱讀后,如果你恰好在開(kāi)發(fā)“值得投入精力編寫(xiě)測(cè)試”的應(yīng)用,不妨思考下,上面是否恰好有符合你所需求的部分。
審核編輯:劉清
-
API
+關(guān)注
關(guān)注
2文章
1504瀏覽量
62157 -
python
+關(guān)注
關(guān)注
56文章
4798瀏覽量
84799 -
DOM
+關(guān)注
關(guān)注
0文章
18瀏覽量
9589 -
JSON
+關(guān)注
關(guān)注
0文章
119瀏覽量
6980
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論