網站建置不是件簡單的事,我們都知道網站做好之後,有好多細節需要兼顧,所以許多公司花了大量的時間與金錢,耗用人力對維護中的網站進行不斷的、重複的人工測試,想達到的目的不外乎是希望網站不要出錯,可以給客戶/使用者最好的網站使用體驗。本篇章說明使用 Vue3 開發的專案,導入 Vitest 進行極速單元測試的體驗!
在不同的測試類型中,所需要保護的面向不太相同,以下是不同測試類型的大致介紹:
以程式碼的最小單位進行測試,保護程式邏輯不會在系統維護的過程中遭到破壞,也進一步確保維護中的程式碼品質。
這種測試類型通常由開發人員自行撰寫,自己寫的 Code 自己寫測試,有經驗的開發人員可以用非常有效率的方式撰寫單元測試,因為測試範圍小,這種類型的測試通常不需要設立測試環境,因此可以得到較高的撰寫效率,也是所有測試類型中最容易撰寫的測試類型。不過,對於一個沒有經驗的開發者來說,撰寫單元測試可能會耗用大量時間,寫測試程式的時間很有可能會遠大於實際撰寫程式碼的時間,有蠻多人會因為這樣而放棄撰寫單元測試。
整合多方資源進行測試,確保模組與模組之間的互動行為正確無誤,也讓不同模組在各自開發維護的過程中不會因為功能調整而遭到破壞。
這種類型的測試通常介於單元測試與端對端測試之間,有時候會由專職的測試人員進行開發,但大部分還是由開發團隊中負責特定模組的人來撰寫。有時候單一模組即便完全通過單元測試,獨立運作也正常,但是當需要與其他模組互動時,也是有可能發生錯誤,這時就是整合測試的主要負責領域。
以下是未通過整合測試的案例:(兩個元件在整合時發現問題)
所謂的「端對端」(E2E) 是指從使用者的角度出發(一端),對真實系統(另一端)進行測試。
這種類型的測試對許多公司來說,就是「人工測試」的主要範圍,因為你可以透過人工對已經完整部屬的網站進行測試,因此可以驗證出系統是否符合客戶的實際需求。這部分也可以透過撰寫 E2E 測試程式來進行自動化,增加測試效率。
這裡有個未通過 E2E 測試的案例相當有趣,雖然每個整套系統每個模組都通過所有單元測試與整合測試,但最後組裝起來後,從使用者的角度無法接受!
describe('貓咪', () => {
it('摸摸,應該會發出「呼嚕嚕」聲', () => {
// ...
})
it('餵食,應該會發出「呼嚕嚕」聲', () => {
// ...
})
it('拿玩具逗,應該會發出「呼嚕嚕」聲', () => {
// ...
})
it('什麼都不做,應該推倒眼前看到的所有東西', () => {
// ...
})
})
當我們設立好測試情境與測試案例的敘述結構之後,要開始撰寫測試案例內部的實作時就可以利用所謂的 3A 模式來安排。
3A 模式主要是為(Arrange-Act-Assert)三個英文字的縮寫,而他們分別代表了:
以上方第一項測試案例來套用 3A 模式的話就會像是:
it('摸摸,應該會發出「呼嚕嚕」聲', () => {
// Arrange: 準備好一隻貓
const target = new Cat()
// Act: 摸摸那隻貓咪
target.touch()
// Assert: 觀察那隻貓是否發出呼嚕嚕叫聲
expect(target.speaking).toBe('呼嚕嚕')
})
那如果這時候你可能會想到每個測試案例都要準備一隻貓貓,對於測試案例來說就會一直不斷地去做「準備(Arrange)」這個行為,因此你可能會很直覺的這麼處理:
describe('貓咪', () => {
const target = new Cat()
it('摸摸,應該會發出「呼嚕嚕」聲', () => {
target.touch()
expect(target.speaking).toBe('呼嚕嚕')
})
it('餵食,應該會發出「呼嚕嚕」聲', () => {
target.feed('乾乾')
expect(target.speaking).toBe('呼嚕嚕')
})
it('拿玩具逗,應該會發出「呼嚕嚕」聲', () => {
target.play()
expect(target.speaking).toBe('呼嚕嚕')
})
})
但這樣的後果就是每個測試案例之間就會有關聯了,比方貓貓其實摸太多下他也會覺得厭煩,從而導致測試失敗:
describe('貓咪', () => {
const target = new Cat()
it('摸摸下巴,應該會發出「呼嚕嚕」聲', () => {
target.touch()
expect(target.speaking).toBe('呼嚕嚕')
})
it('再摸一次下巴,應該會發出「呼嚕嚕」聲', () => {
target.touch()
expect(target.speaking).toBe('呼嚕嚕')
})
it('再摸一次下巴,應該會發出「呼嚕嚕」聲', () => {
target.touch()
expect(target.speaking).toBe('呼嚕嚕') // 預期呼嚕嚕,結果貓咪生氣了
})
})
而要寫好測試案例的其中幾個概念就是要盡量讓每個測試案例之間「不受順序影響測試結果」與「保持獨立」。
因此大多數的「測試環境」的工具都會提供類似相關的 API 來協助處理測試開始前的「Setup」與結束後的「Teardown」環節。
describe('貓咪', () => {
const target = new Cat()
beforeEach(() => {
// 每個測試案例開始前要做的事情
target.init() // 初始化貓貓的各種狀態
})
afterEach(() => {
// 每個測試案例結束後要做的事情
})
it('摸摸,應該會發出「呼嚕嚕」聲', () => {
target.touch() // 這時候的 target 已經是經過 init() 的版本了
expect(target.speaking).toBe('呼嚕嚕')
})
it('餵食,應該會發出「呼嚕嚕」聲', () => {
target.feed('乾乾') // 這時候的 target 已經是經過 init() 的版本了
expect(target.speaking).toBe('呼嚕嚕')
})
it('拿玩具逗,應該會發出「呼嚕嚕」聲', () => {
target.play() // 這時候的 target 已經是經過 init() 的版本了
expect(target.speaking).toBe('呼嚕嚕')
})
})
綜合上述 3A 與處理 Setup & Teardown 的觀念,之後再寫測試案例時,我們可以先從基礎的 3A 模式開始撰寫,而到有需要處理重複的事前準備(Setup)與後續清理時(Teardown),就可以藉由工具來替我們統一處理。
看到這邊讀者應該會發現,測試的基本概念其實不難懂,而在瞭解測試的概念後,剩下的就是把概念轉換為測試工具可讀懂測試程式碼就好了!
因文章篇幅限制,轉站到我的部落格繼續閱讀...
作者:Wayne (偉恩)
連結:https://wayne-blog.com/
來源:Wayne's blog | 偉恩的部落格 | 技術博客