“叕”談 TDD(三)--- 如何TDD

背景:

之所以要 “叕” 談 TDD, 除了上述背景,也是因為自己工作4年來,雖然經常聽到 TDD,但著實沒有“完整” 的在項目上實踐過它。直到最近打算在當前的交付項目上實踐,才又重新審視 這項實踐,以求回答下列問題:

在逐一回答這些問題之前,先說我對 TDD 這種實踐的 觀點

  • TDD 是確保 Dev 在編寫代碼時,處于 對需求保持 “清醒(Obvious)” 狀態 的方式之一,但并非 唯一 方式
  • TDD 中的測試(T)要面向業務需求,而非代碼實現
  • TDD 是一種 快速, 可復用 的 反饋獲取 方式,而非唯一方式
  • 如果能 不用 TDD 并做到上述 3 點,那么不 TDD 也沒問題。

如何 TDD

其實 TDD 實踐方式的大體輪廓在上一篇《為何TDD》中的代碼示例已經提及一二,本篇不過是增添細節,將其更加完整、詳盡地展示出來。
簡單來說,TDD 可以有兩種實踐模式:

  • 單人模式(Solo)
  • 雙人模式(Pair)

它們會給你完全不同的體驗。從整體來看,我將 TDD 的實踐歸納為 3個階段

  • 準備階段
  • 實施階段
  • 收尾階段

每個階段都有明確的目標和需要掌握的輔助技能,下面我會基于 單人模式 進行詳細介紹。雙人模式如無專門說明,則與單人模式的實踐相同。

準備階段

1. 理解需求
這是一個非常容易遺漏的步驟,因為需求“理應”已經完全標明在故事卡(User Story)中了,只要看完卡片不就好了嗎?
其實不然。
只是讀完卡片,并不能說明需求被完全理解了。在這一步,要做到明白當前卡片的價值何在,同時還需清楚卡片上的驗收條件是否足夠完整驗證卡片價值的達成。在此之后,如果卡片上的驗收條件還不包含了可以使用的具體樣例(數值),而只是抽象的公式或邏輯,那么我們需要將這些抽象的公式或邏輯具象化,形成后續寫測試時可以直接使用的測試數據

例如 有????的需求描述

作為 話費充值用戶,
我想 在查詢話費時,能夠看到當前賬戶余額,
以便 我能夠了解何時應當進行話費充值 

可能的驗收條件

驗收條件1:
給定 一個非欠費用戶,
當 該用戶查詢賬戶余額時,
即 給出當前實際的賬戶金額,賬戶金額應大于等于 0
----------------------------------------
驗收條件2:
給定 一個欠費用戶,
當 該用戶查詢賬戶余額時,
即 給出當前的欠費金額,欠費金額用負數表示,
并 提示友好信息通知用戶充值

在理解需求的過程中,需要具象化上述驗收條件,于是可以發現 友好信息的內容尚不明確,于是通過進一步溝通和確認,明確了內容:欠費大于 50 元,則停機,友好信息的內容也會有所差別,即

未停機: “您的余額不足,為了避免停機造成的影響,請盡快繳存話費”
已停機: “您當前處于停機中,繳費后恢復服務”

于是,基于邊界條件,可以整理出如下的具象化用例(Exapmle)

對于 驗收條件1:
Example 1:
給定 一個賬戶余額為100的用戶
當 該用戶查詢賬戶余額時,
即 給出當前余額為 100.00

Example 2:
給定 一個賬戶余額為0的用戶
當 該用戶查詢賬戶余額時,
即 給出當前余額為 0.00
--------------------
對于 驗收條件2:
Example 3:
給定 一個欠費10元的用戶,
當 該用戶查詢賬戶余額時,
即 給出當前余額為 -10.00,
并 提示“您的余額不足,為了避免停機造成的影響,請盡快繳存話費”

Example 4:
給定 一個欠費50元的用戶,
當 該用戶查詢賬戶余額時,
即 給出當前余額為 -50.00,
并 提示“您的余額不足,為了避免停機造成的影響,請盡快繳存話費”

Example 5:
給定 一個欠費50.01元的用戶,
當 該用戶查詢賬戶余額時,
即 給出當前余額為 -50.01,
并 提示“您當前處于停機中,繳費后恢復服務”

注: Example 2 和Example 4 用于驗證驗收條件邊界處的滿足與否。

至此,才算需求被理解了。

2. 明確當前系統的測試策略
通常,每個系統都會有自己的架構,而這些架構也都會分成不同的層級,每個層級都會有相應的一些組件。那么在開始TDD 之前,我們一定要先弄清楚針對當前工作的系統架構,它的測試策略是什么?

這就需要用到測試金字塔理論測試四象限理論

先弄明白針對目標系統的測試策略,就可以消除TDD 過程中,對于測試粒度不清楚的問題,即我是該寫單元測試呢?還是該寫組件測試?又或者其他什么測試類型?

通常,測試策略很難有定式,需要“因地制宜”,結合測試目的,成本,具體問題具體分析,具體制定,這里就不多作說明了。

基于上述理念,特別強調我們要避免認為TDD 就一定要從單元測試(Unit Test)做起.

3. 拆分任務
有了一個個具象化的用例,和明確的測試策略,“任務”的目標就很明確了:

將具現化的用例轉化為符合測試策略的測試代碼,并通過測試

但這只是一個非常宏觀的“任務”,它并不是我們在在任務拆分時需要完成的任務。因此,在拆分任務前,我們需要思考任務的應具備的特征:

  • 可達成:任務最終是一定能夠完成的
  • 可驗收:每個任務的完成與否都有明確的衡量標準
  • 可估時間:完成任務所需的時間是可以被大概估計的,可以使用TimeBox追蹤
  • 目標相關:完成了這些任務,那么這些任務所對應的目標就能被實現

每一個在時限內完成的任務,都是一次“正向反饋”,它會為開發者提供成就感,從而使開發者進入一種“節奏”,有時通過這種節奏,開發者可以更容易地進入“流”(Flow,一種注意力高度集中的狀態)。
而每一個時限內未能完成的任務,則都是一次“負向反饋”,它為開發者提供反思的入手點,從而歸納總結出可以進一步提升的知識、技能等個人能力。

我通常在 TDD 的實踐中,會將任務拆分到可以在15 - 30 分鐘內完成的大小。如果利用需求理解部分的例子具象化這樣的一個任務,那么在一個傳統的 MVC 分層架構的后端系統中,我的任務拆分結果會是這樣:

任務1: 完成 驗收條件 1 中的功能,通過 Example 1 和 Example 2 的驗證,并通過后端 API 返回期待的結果(20 分鐘)
任務2: 完成 驗收條件 2 中的功能,通過 Example 3, Example 4, Example 5 并通過后端 API 返回期待的結果(30 分鐘)

至此,我們的準備階段就結束了,可以進入接下來的實施階段了。

注:雙人模式下,準備階段會增加更多的討論,這些討論在一定程度上是有助于探索遺漏的邊界條件,但同時也需要控制討論的效率,求同存異,以防對整體工作造成影響。

實施階段

進入實施階段后,我們就可以帶上一頂名為“實現功能”的帽子,專注業務功能實現,開始代碼編寫了。

《為何TDD》中,我有貼過一些 TDD 方式產出的代碼。這里,就不再貼出額外的代碼了。但是,可以嘗試利用基于傳統的 MVC 分層架構中的核心業務層(Service),單元測試的作為該層組件的測試策略的場景,總結如下的 TDD 實踐步驟以供參考:

1. 定義目標 Service 類,**簡單設計**類中所需方法的簽名
2. 構造目標 Service 類的測試,根據測試需要,Stub/Mock/Spy/Fake/Dummy(測試替身)當前Service 的依賴項(可以是 Repository 接口,HttpClient 接口,Config接口等)
3. 利用具象化的 Example 中的內容,目標方法的簽名和已聲明的測試替身,編寫測試用例,并運行
4. 調整 Service 類中定義的方法邏輯,通過測試
5. 重復 3,4 步驟,直到之前所有的列出的 Example 都被“翻譯”為測試代碼,并被運行通過

在實施階段的工作完成后,新添加的代碼理應已經可以完全滿足業務需求,并且所有的業務需求,也都已經被“翻譯”為測試代碼。這意味著,無論如何調整代碼,只要已有的測試用例能夠全部通過,當前的業務功能就不會受到任何影響。
那么,我們就可以放心的拿下“實現功能”的帽子,進入最后的收尾階段。

注: 雙人模式下, 實施階段需要合理分配工作,可以采用工作經驗較豐富或對當前業務更熟悉的一人來“翻譯”測試(編寫測試用例),另一人則專注于通過測試。并在合適的時機進行角色交換,平衡兩人的參與感。更多細節,可以參照《沉思錄---結對編程篇》

收尾階段

在收尾階段中,開發者需要帶上一頂名為“重構”的帽子。此時,有了充足的測試覆蓋的保證,開發人員可以“為所欲為,大刀闊斧,隨心所欲”的使用學到的設計模式,技巧,基于整潔代碼的規范,優化代碼,消除“實現功能”過程中遺留的“壞味道”,使其更易讀、易維護。

總結

如何TDD?

  1. 理解需求,將需求通過驗收條件,轉化為具象化的Example
  2. 明確測試策略,結合測試金字塔與測試四象限,設計與測試意圖、成本匹配的測試策略
  3. 拆分任務,基于需求和任務的特性,對業務需求目標進行拆分
  4. 利用具象化的Example,測試策略,以“翻譯”需求為目的編寫測試,并以通過測試為目的實現功能
  5. 通過重構,優化代碼,完成收尾工作
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容