在開始撰寫測試之前,先帶大家來了解一下 Angular 預設使用的測試框架 ─ Karma 。
Karma 的原名是 Testacular , Google 在 2012 年的時候將其開源, 2013 年時將其改名為 Karma ,它是基於 Jasmine 與 Selenium 所開發出來的 JavaScript 測試執行過程管理工具(Test Runner)。
一般我們會使用它來撰寫單元測試與整合測試,測試的檔案名稱通常會命名為 xxx.spec.ts
,而只要是使用 Angular CLI 所建立的檔案,在預設的情況下都會連帶產生該檔案,像是: xxx.component.spec.ts
、 xxx.service.spec.ts
。
當我們想要執行測試程式時,只要使用指令 npm test
or yarn test
or ng test
,就可以看到它的執行結果:
當 Karma 執行起來後,只要我們不停掉它的 server 且不關掉它的視窗,只要我們有修改我們的測試並存檔後,它就會偵測到我們的變動並再重新跑一次測試,是個很方便且強大的功能。
關於執行測試時的更多參數,請參考Angular 官方 API 文件
想了解更多的話,可參考網路文章:JavaScript 測試工具之 Karma-Jasmine 的安裝和使用詳解與 Karma 官方文件
測試的檔案內容
上述提到,在 Angular 裡的測試檔案一般我們會將其命名為 xxx.spec.ts
,而檔案內容大致上會長這樣:
或是這樣:
從中我們可以發現,它是一種巢狀式的結構,外層會是一個名字叫 describe
的函式,內層則有許多名為 it
的函式,這些函式各是什麼意思呢?
it
it
指的是 測試案例(Test case),通常會在 describe
函式的裡面,使用方式如下所示:
1 | it('說明文字', () => { |
第一個參數是該測試案例的說明文字,讓我們在閱讀時可以很清楚、直接地知道這個測試案例會有什麼結果,通常建議以 should
做開頭,整體閱讀起來較為順暢,例如:
1 | it('should be created', () => { |
或者像是:
1 | it('should have as title "Angular"', () => { |
第二個參數是一個函式,裡面就是該測試案例所要執行的程式碼,也就是我們實際上要測試的內容。
describe
describe
指的是 測試集合(Test suite),主要是用於將測試案例分組、分類,類似資料夾的概念,這樣我們在閱讀程式碼的時候與其測試結果時,才會比較好閱讀
使用方式如下所示:
1 | describe('說明文字', () => { |
跟 it
一樣,第一個參數是該測試集合的說明文字,讓我們在閱讀時可以很清楚、直接地知道這個測試集合的主要測試目標,例如:
1 | describe('LoginComponent', () => { |
第二個參數是一個函式,裡面是該測試集合所要執行的測試案例。
describe
除了分類、分組的功能外,他還有一個很重要的特性 ─ 作用域(Scoping) 。
作用域(Scoping)
在寫測試案例的時候,我們可能會遇到某些情況是在需要事先做一些配置,又或者是驗證完之後需要把某些狀態還原,如果將這些事情寫在每一個 it
裡又覺得很囉嗦且不好維護,這時候我們就會使用以下這些函式來幫我們:
beforeAll
─ 在執行所有的測試案例之前,會先執行這裡面的程式碼。beforeEach
─ 在執行每一個測試案例之前,會先執行這裡面的程式碼。afterAll
─ 在執行完所有的測試案例之後,會再執行這裡面的程式碼。afterEach
─ 在執行完每一個測試案例之後,會再執行這裡面的程式碼。
舉個例子,如果我們有個測試集合長這樣:
1 | describe('Test Suite', () => { |
它的執行結果會是這樣:
1 | // beforeAll |
從上述結果中可以看出,在一個測試集合裡會先執行的是 beforeAll
裡的程式,接著會是 beforeEach
,然後才會是測試案例;而在測試案例之後,則會先執行 afterEach
才會輪到下一個測試案例之前的 beforeEach
,再接著下一個測試案例,之後一樣會是那個測試案例之後的 afterEach
。直到最後沒有測試案例時,就執行 afterAll
裡面的程式,結束這個測試集合。
有比較理解了嗎?如果有的話,我們來試試比較複雜一點的巢狀結構:
1 | describe('Test Suite - 1', () => { |
它的執行結果會是這樣:
1 | // beforeAll - 1 |
為讓大家比較好閱讀,我將每個測試案例稍微隔開方便大家觀察其中規律。
雖然這個例子比較複雜,但邏輯上來說跟上一個例子一樣:在開始測試某測試集合裡面的測試案例之前,會先執行該測試集合的 beforeAll
,接著是每一個測試案例的 beforeEach
,然後執行測試案例,執行完測試案例後就是 afterEach
。
比較特別需要注意的就是當要開始執行 test case - 3
之前,會先執行的是 Test Suite - 2
的 beforeAll
。原因就像上面提過的:「在開始測試某測試集合裡面的測試案例之前,會先執行該測試集合的 beforeAll
」, test case - 3
是 Test Suite - 2
裡面的測試案例,所以在開始測試 test case - 3
之前,自然會先執行該測試集合裡的 beforeAll
,接著是父層測試集合裡的 beforeEach
,才會輪到 Test Suite - 2
裡面的 beforeEach
。
這個概念在大多數的前端測試框架裡是差不多的,學一次基本適用在大多數的測試框架裡, CP 值非常之高。
雖然上述的測試執行過程看似有序,但實際上我們不能依賴這種有序,原因跟如何撰寫出優秀的測試有關,不過相信今天的內容應該已經夠燒腦了,所以明天再跟大家分享如何撰寫出優秀的測試吧!
本日小結
今天的文章內容主要是要讓大家在開始撰寫測試之前,先對 Angular 的測試框架、測試檔案的內容結構有個初步的理解,如此一來有兩個好處:
- 後續不用再解釋,文章內容可以比較精簡
- 有需要時可以回來複習
此外,今天的重點主要是以下三點:
- 認識 Angular 預設所使用的測試框架。
- 了解測試檔案的內容結構。
- 理解作用域(Scoping) 的邏輯。
尤其是關於作用域(Scoping) 的部份,這在後續撰寫測試時,會非常常使用,所以如果有任何的問題或是回饋,請務必留言給我讓我知道噢!