1. 程式人生 > >五分鐘讓你徹底瞭解TDD、ATDD、BDD&RBE

五分鐘讓你徹底瞭解TDD、ATDD、BDD&RBE

在目前比較流行的敏捷開發模式(如極限程式設計、Scrum方法等)中,推崇“測試驅動開發(Test Driven Development,TDD)”——測試在先、編碼在後的開發實踐。TDD有別於以往的“先編碼、後測試”的開發過程,而是在程式設計之前,先寫測試指令碼或設計測試用例。TDD在敏捷開發模式中被稱之為“測試優先的程式設計(test-first programming)”,而在IBM Rational統一過程(Rational Unified Process,RUP)中被稱為“測試優先的設計(test-first design)”。所有這些,都在強調“測試先行”,使得開發人員對所做的設計或所寫的程式碼有足夠的信心,同時也有勇氣進行設計或程式碼的快速重構,有利於快速迭代、持續交付。重構的前提就是測試就緒(testing is ready),在這樣的前提下,重構的風險就很低,否則就有比較高的風險。

TDD具體實施過程,可以看作兩個層次,如圖1所示:

  1. 在程式碼層次,在編碼之前寫測試指令碼,可以稱為單元測試驅動開發(Unit Test Driven Development,UTDD)
  2. 在業務層次,在需求分析時就確定需求(如使用者故事)的驗收標準,即驗收測試驅動開發(Acceptance Test Driven Development,ATDD)。
圖1  TDD的兩個不同層次

先來討論UTDD,如圖2 所示。在打算新增某項新功能時,先不要急著寫程式程式碼,而是將程式可能會碰到的特定條件、邊界值、上下文等想清楚,為待編寫(類或方法)的程式碼先寫好測試指令碼。然後,利用整合開發環境或相應的測試工具來執行這段測試用例,結果自然是通過不了(失敗)。利用沒有通過測試的錯誤資訊反饋,瞭解到程式碼沒有通過測試用例的原因,有針對性的逐步地新增程式碼。為了要使該測試用例通過,就要補充、修改程式碼,直到程式碼符合測試用例的要求,獲得通過。測試用例全部執行成功,說明新新增的功能通過了單元測試,可以進入下一個環節。這樣的流程也適合程式碼修改或重構,真正執行時,也不會嚴格按照這樣的流程去做,但最基本要求是:先寫好測試指令碼(程式碼),再寫產品程式碼並通過測試。按照UTDD做法,不是先寫產品程式碼的類,再寫測試類,而是先寫測試類,再寫產品的類。 

圖2   UTDD執行的過程

UTDD從根本上改變了開發人員的程式設計態度,開發人員不能在像過去那樣隨意寫程式碼,要求寫的每行程式碼都是有效的程式碼,寫完所有的程式碼就意味著真正完成了編碼任務。而在此之前,程式碼寫完了,實際上只完成了一半工作,遠沒有結束,因為單元測試還沒執行,可能會發現許多錯誤,一旦缺陷比較多,缺陷就比較難以定位與修正。UTDD在於促進開發人員思考功能特性的應用場景、異常情況或邊界條件,寫出更完善的程式碼,避免犯較多的錯誤。其次,也確保測試具有獨立性,不受實現思維的影響,確保測試的客觀、全面。這一點,對開發人員測試自己的程式碼是必要的。如果是倒過來,先寫產品程式碼(即功能實現在前)再進行測試,那麼測試會受實現思維影響

。例如,我們自己寫的文章自己檢查,有時很明顯的問題都發現不了,就是受實現思維的影響。一般來說(多數情況下),開發人員測試自己的程式碼有兩個障礙:思維障礙和心理障礙。心理障礙是指開發人員對自己的程式碼不會窮追猛打,發現了一些缺陷,很可能會適可而止。我們知道,實際上缺陷越多的地方越有風險,越要進行足夠的測試。最後,UTDD也確保所有程式碼的可測試性,每一行程式碼得到了測試,比較徹底地確保程式碼的(微觀)質量。

許多研發人員不習慣UTDD這種模式,推行UTDD會遇到比較大的困難,那TDD的實施可以移到業務層,推行ATDD,即在設計、寫程式碼之前,明確系統功能特性的驗收標準,這比較容易推廣實施。例如,在敏捷開發模式中,每個使用者故事的描述過於簡單,是不具有可測試性的。例如,開發一個線上旅遊網,可以提供交通、酒店、門票等預定服務,有一個最基本的使用者故事:

作為一個旅行者的使用者,我想通過一次性的操作,快速刪除事先預定的訂單包(含飛機票、酒店和門票)

像這樣的使用者故事,如果不加驗收標準,開發實現起來很容易,在資料庫某個表中刪除一條記錄,在其它關聯表上修改相應的標誌位即可。但實際的業務不會那麼簡單,說取消就取消?不需要有一個時間提前量?取消一定成功嗎?收不收相關的費用?是否需要線下處理的時間?是否需要通知使用者?通過什麼方式通知取消成功或失敗?要回答這些問題,就是要給這個使用者故事增加“驗收標準”,如:

  • 取消前,需要提醒使用者再次確認
  • 需提前24個小時取消
  • 需要4個小時處理時間,才能知道取消成功與否
  • 這類取消需要收取總金額10%的費用
  • 不管取消成功與否,採用郵件和簡訊雙重通知
  • 使用者事後可以查詢取消的相關記錄
  • 需要保留客戶和旅行網雙向操作記錄日誌

這樣,這個使用者故事才具有可測試性,開發人員也會清楚如何實現這個使用者故事,實現的結果和產品經理所期望的結果就不會有太大差異。

從ATDD演化出來一種具體落地的開發模式就是BDD(Behavior Driven Development,行為驅動開發)。BDD只是將驗收標準更加明確化,可以看作是ATDD的例項化即列出使用者故事所可能遇到的應用場景,而且將這種應用場景的表達方式規定為GWT格式,即

BDD再往前推進一步,就是需求例項化(Requirements By Example,RBE),更加明確需求的具體表現。還是以上面使用者故事為例,可以建立類似下列內容的需求例項化。

需求越明確,使用者、產品經理、開發與測試等之間的理解就越一致(on the same page),更不產生偏差和誤解,有利於開發和測試的工作。基於RBE,開發人員寫產品的程式碼,測試人員可以獨立寫測試的程式碼,產品經理的工作也會變得輕鬆,不需要太多的解釋、不需要回答開發和測試的各種問題。

  • 從需求角度看,BDD和需求例項化比較徹底地明確需求,統一使用者、產品經理、開發與測試等認識,讓大家處在一個層面上,使研發工作更高效。
  • 從測試角度看,需求即測試,產品的需求就是測試的需求,需求可以被執行,即一步到位,將需求變為自動化測試指令碼,開發出來的功能特性隨時可以被自動驗證。

TDD一改以往的破壞性測試的思維方式,測試在先、編碼在後,更符合“缺陷預防”的思想。這樣一來,編碼的思維方式發生了很大的變化,編寫出高質量的程式碼去通過這些測試,在進行每項設計、寫每一行程式碼時都要想想使用者的真實需求、應用場景和一些例外等,確保實現的功能特性符合預期,並具有健壯性。測試,也從以前的破壞性的方法轉移到一種建設性的方法中來。在這種積極心態的影響下,開發人員的工作效率和產品的質量都會有顯著的提高,真正實現“質量是內建的(Quality is built in)”的目標。