Why ATDD?
我以前總覺得 TDD 是個不切實際的軟體開發流程,可以遵守 TDD 的團隊除了都有一定的技術底子之外,大概也都需要很自律⋯⋯不然怎麼會有力氣花時間想好要寫什麼測試、測資有哪些?
但最近在學到 User Story、Acceptance Criteria 的概念之後,一切突然都變得合理起來了!以前會不想寫測試的原因就是不知道 test case 要寫什麼、對 production code 的規劃都迷迷糊糊的。因此只好邊寫邊想下一步,導致難以規劃測試、程式碼架構也不乾淨的窘境,時間長了就容易陷入技術債的負面循環當中;或是會因為對於要滿足的需求充斥五花八門的想像,而導致 over design。
ATDD 當中的 A 是以 Acceptance Criteria (驗收標準) 為準的 TDD,引入 ATDD 之後可以帶來的好處就是透過統一溝通語言,讓非工程人員的需求有效傳達到工程人員手上,在降低溝通成本的同時讓軟體設計更加 fit 需求。同時也避免工程師在跑 TDD 時,因為不知道要寫什麼測試、又有 Test Coverage KPI 要達成,而硬寫不必要的測試來湊測試總數。
在開始之前,讓我先給一個宏觀的流程:Overview
Overview
利益關係人的需求會透過 User Story 來明確表達,而建立在這些需求之上的會是 AC(Acceptance Criteria),有了 User Story 跟 AC 之後我們就可以來跑 TDD 啦,先依據需求跟驗收標準來寫測試,再來寫出相對應的 production code!
所以很粗略的來說主要是一個 User Story 底下會有多個 AC 需要被滿足,我們可以用這些 AC 作為 TDD 的目標,透過不斷地完成 AC,我們就會持續推動開發進度,直到該 User Story 被滿足。一旦通過了 E2E Test 就代表我們對開發出來的功能有信心,可以交付出去囉!
接下來就按照順序來說明 User Story、實例化需求、AC,最後再來做個總結。
User Story:具體描述問題與需求
User Story 是一種用於傳達功能需求的方法,它從需求方的視角描述需求和期望的結果。良好的 User Story 應該要簡潔並具體,才能夠清楚地描述用戶的需求,同時也便於開發團隊理解和實作。
格式:
- As a __, I want to __, so that __.
- 作為 [使用者角色]
我想要 [功能需求]
以便於 [實現的目標]。
假設我收到了來自 PM 的需求,他希望提出一個功能讓使用者可以查看產品的評論和評價,在經過討論之後我們可以精煉成下面這段的描述:
作為 一名電商網站買家,
我想要 在每個產品頁面上查看其他買家的評論和評分,
以便於 我可以快速獲得產品的品質和了解其他買家的具體使用感受,這將幫助我決定是否購買該產品。
收到了上述的 User Story,作為一個工程師你可以很清楚地瞭解某某功能是為了某某價值,並且是設計給某某角色的。有了 User Story 這個骨幹之後,我們就有一條 Happy Path 可以來做 E2E Test 了,但這樣就夠我們下筆開始寫測試了嗎,我自己在收到這種需求後會覺得似有道理但又不確定怎麼做,模糊地帶其實很多⋯⋯
像是評論會包含哪些項目?是純文字還是會有圖片?有限制字數嗎?評分有限定要整數還是小數嗎?為了避免這些模糊地帶造成功能與實際上的需求產生落差,造成額外的開發、修改成本,我們接下來會透過實例化需求來梳理需求的真實面貌。
實例化需求:溝通時避免抽象用詞、多多舉例
不是把 User Story 訂出來就可以丟給工程師開發了,一方面工程師可能會需要腦補很多東西,另外最怕的就是費盡千辛萬苦把功能做出來之後,才發現跟 PM / 客戶要的東西不一樣!但在開會的時候,不管是 Project Owner 或是 Developer 都可能有腦袋不清楚的時候,用例子來溝通有助於避免彼此以為互相理解了,但是在開發後期才發現專案與需求脫鉤、需要進行大幅度的更改。
讓我們再看一次剛剛的 User Story:
作為 一名電商網站買家,
我想要 在每個產品頁面上查看其他買家的評論和評分,
以便於 我可以快速獲得產品的品質和了解其他買家的具體使用感受,這將幫助我決定是否購買該產品。
也就是說今天這個查看評論的功能是為了幫助買家決定要不要購買產品的重要功能,我們可以從這裡出發,去思考我們的功能要怎麼幫助到買家。
我的思路是這樣的,如果我是使用者的話我會希望可以快速看到有代表性的評論、知道這個商品的評價概況。讓我實例化需求,給出幾個例子:
- 產品頁面上應顯示產品的平均評分:
- 假設有三個使用者分別給出了 3 / 4 / 5 分,那我需要在產品頁面中秀出 4 分。
- 產品頁面上應顯示前三條最多讚數的評論:
- 假設評論 A / B / C / D / E 分別獲得 1 / 2 / 3 / 4 / 5 個讚,那我們會秀出評論 C / D / E。
- 每條評論必須包括評分、評論內容、評論讚數、評論者名稱和評論日期:
- 評分:4.5 星
- 評論讚數:100 讚
- 評論內容:這個產品⋯⋯(下略)
- 評論者名稱:Robert
- 評論日期:2024/6/18
不知道你會不會納悶:為什麼連「顯示前三條最多按讚的評論」這種非常直觀的敘述都需要舉例呢?我覺得看情況而定,規則不是拿來死命遵守的。但我認為舉例的好處就是可以讓需求方的想像具象化,也就是說可能有些需求在舉出例子之後反而會撤回或是再修改,降低開發後功能修改的可能性。
列舉有效、無效、邊界測資
另外在實例化需求中,很重要的是舉出有效、無效、邊界的測資,像是我們在看到 Valid Input 2 的例子之前,可能不會特別思考要是「產品沒有任何評論」的話,系統應該如何處理呢?
- Valid Input 1: 正常評論資料:產品有幾條正常範圍的評論
- 星級介於 1 到 5 之間,並且以 0.5 顆星為單位
- 評論內容包含 50-300 個字
- 評論應該依據讚數進行排序
- 預期結果:系統應該在產品頁面上正確顯示平均評分和前三條評論的內容。
- Valid Input 2: 新產品無評論
- 產品剛上架,還沒有任何用戶評論。
- 預期結果:系統應顯示「暫無評論」。
再來尤其是無效跟邊界的測資列舉非常重要,因為這關乎到系統應該怎麼對這些測資進行處理,錯誤處理往往是開發系統中最棘手的地方。
- Invalid Input 1: 超出星級範圍
- 描述:評論中的星級為 0 或 6。
- 預期結果:系統應該拒絕這種輸入並顯示錯誤訊息。
- Invalid Input 2: 評論內容異常長
- 描述:評論字數超過上限 300 字。
- 預期結果:系統應該對顯示的評論內容進行截斷,只顯示前 300 個字。
- Edge Case: 剛好三條評論
- 描述:產品剛好有三條評論。
- 預期結果:系統應該完整顯示這三條評論。
- Edge Case: 評論邊界字數
- 描述:評論字數剛好在最小(例如1個字)和最大(300個字)邊界。
- 預期結果:系統應該能夠處理並顯示這些評論。
透過實例化需求,不僅團隊在溝通上更容易對齊對需求的共識,也大幅降低需要腦補 / 通靈需求的成本,是不是非常好用啊!
Acceptance Criteria: 驗收標準
有了 User Story 以及實例化需求後,我們就可以加入驗收標準來檢核功能是否達到客戶 / PM 對需求的期待了。在 AC 中的標準應該是具體的、可測量的,並能夠明確指出功能必須達到的要求以被視為完成,這樣我們才容易轉換為顆粒度小的單元測試
根據前面實例化需求舉出的例子,我們可以歸納兩個方面的 AC:評分、評論
- 產品評分顯示:
- AC1: 每個產品頁面必須在頁面中顯示產品的平均評分。
- AC2: 平均評分將根據所有用戶評分計算得出,並四捨五入到最近的 0.5 星。
- 用戶評論顯示:
- AC3: 評論應該依據讚數進行排序
- AC4: 每個產品頁面必須顯示至少三條最多讚的評論,如果少於三條評論則顯示現有的所有評論。
- AC5: 每條評論必須包括評分、評論內容、內容讚數、評論者名稱和評論日期。
- AC6: 如果一個產品沒有任何評論,應顯示「暫無評論」的訊息。
- AC7: 評論內容如果超過 300 字,系統應自動截斷。
這些驗收標準將作為接下來跑 TDD 的基礎。在決定要先從哪個 AC 開始的時候,我主要是考慮 AC 的業務優先順序。可以先考慮哪些 AC 對功能比較關鍵、如果沒有這些 AC,功能會無法正常運作。例如 AC1「每個產品頁面必須在頁面中顯示產品的平均評分」、AC5「每條評論必須包括評分、評論內容、內容讚數、評論者名稱和評論日期」就分別包含了功能中最基礎的元素:評分跟評論的規範,先完成這些基礎規範我們才能繼續往下寫功能。
透過制定 AC 可以確保開發出來的功能盡可能貼近業務需求。當這些標準全部滿足時,我們就對自己所開發的功能有信心交付了。
總結:Top-Down 再 Bottom-Up
在了解完 User Story、實例化需求、AC 之後,我們可以很粗略的這樣理解他們的階層關係:最高階層的 User Story 產出後,為了釐清需求,我們會透過實例化需求來進行溝通。在對齊需求共識後,就可以定下 Acceptance Criteria,以便之後在進行 TDD、錯誤處理時有個參考的依據,減少模糊地帶、通靈的空間。到目前為止是由高到底層的 Top-Down 規劃。
在實作時,會先透過 End-to-end Test 來寫出滿足 User Story 的 Happy Path,在跑大圈的流程中不會測試例外的情況,而是會將側例外處理交給成本較低的單元測試或是整合測試,End-to-end Test 測試只會著眼在滿足 User Story 的需求;也就是說想辦法推動圖中大圈的循環是我們開發的目標。而為了推動進度,我們會透過跑好幾輪小圈的 TDD 來確保 AC 有過,並且開發出來的 production code 都有滿足當初設下的驗收標準。
引進 ATDD 之後,就結束了嗎⋯⋯?
在了解完如何透過 ATDD 建立完善的開發流程之後,還有很多會面臨到的問題:像是單元測試中的測試替身應該如何使用?如何寫乾淨的測試、提高測試的維護性?測試的範圍應該如何制定?我覺得上述問題都是在實際執行 ATDD 時可能要研習的知識、可以單獨拉出來一篇主題講,期待之後還有機會跟大家分享這些知識!