可理解的代碼非常重要,測試代碼也是如此。在我看來,優秀的測試代碼,必須做到一個重要的事情就是保持測試邏輯的清晰。一個完整的測試案例通常包括三個部分:
1. SetUp
2. Exercise
3. Verifiy
4. TearDown
一 個測試案例如果能清晰的區分這三個部分,其實已經成功了一半。但是,如果僅僅只是做到這一步,離我們的“可理解的”測試代碼還有些距離。在我看來,要做到 測試代碼的可理解,首先要做到的是使用簡潔清晰的方式表達每個測試案例的測試過程。我們都不希望在一個測試案例中看到一個long long method,因為我們需要去理解,你調用了那麼多方法,做了那麼多處理,到底是在幹啥?當然,我們也不希望看到一個極端情況:一個測試案例中就看到一個 函數調用。這樣做確實做到了測試案例表面上的簡潔,但是,卻失去了表現內部細節的機會。我們需要表述的是測試過程和方法,比如,進行了什麼樣的操作,輸入 了什麼樣的資料,然後得出了什麼樣的結果,和預期結果對比怎麼樣等。如果把這些都封裝到一個方法,別人只能理解你可能會做什麼,但很難去理解你將如何去 做。其次,在測試案例fail的時候,能夠清晰準確的定位問題。極限情況下,一個測試案例裡應該只能有一個斷言,或者說一個檢查點。測試粒度的縮小能夠加 快問題的定位。通常,當一個測試案例fail的時候,我們還需要定位到底是測試代碼的問題還是被測代碼的問題。這是一個煩人的過程,定位問題的人不一定就 是寫這段測試代碼的人,就算是寫這段測試代碼的人,也需要時間去理解測試案例幹了什麼。這時,測試過程及方法的清晰表述就變得尤為重要。
有的人會 想到通過文檔來表述測試過程和方法,在我看來,這不是一個好辦法。敏捷開發中的一個思想:“一個過時的文檔比沒有文檔更加糟糕”。為什麼需要文檔才能表述清楚你的測試過程? 只能說明你的測試代碼足夠糟糕,無法讓人理解。一個糟糕的代碼就算當初寫下了足夠詳細的說明文檔,隨著時間的交替,人員的變更,代碼的修改維護,複雜的文 檔和代碼會變得越來越不同步。導致的結果將會是代碼越來越糟糕複雜,文檔越來越過時。最好的文檔,其實是代碼。
我們先來看一下一個老外寫的完整的測試案例代碼:@Test
public void shouldBeAbleToEditAPage() {
Given.thatThe(wiki).wasAbleTo(beAtThe(PointWhereItHasBeen.JUST_INSTALLED));
And.thatThe(user).wasAbleTo(navigateToTheHomePage());
When.the(user).attemptsTo(changeTheContent().to("Welcome to Acceptance Test Driven Development"));
Then.the(textOnTheScreen().ofThe(user)).shouldBe("Welcome to Acceptance Test Driven Development");
}
我 之前並沒有為這個測試案例做任何說明,相信每個看過這個測試案例的人都能非常容易的理解這個測試案例的行為,甚至是對代碼不通的人。因為上面的測試代碼使 用了幾乎是自然語言的方式,描述了其測試的過程。瞭解的人一定看出來了,其實,這就是行為驅動開發,Behavior Driven Development,簡稱:BDD。
行為驅動開發(Behavior Driven Development)
Behaviour-Driven Development (BDD) is an evolution in the thinking behind TestDrivenDevelopment and AcceptanceTestDrivenPlanning.
It brings together strands from TestDrivenDevelopment and DomainDrivenDesign into an integrated whole, making the relationship between these two powerful approaches to software development more evident.
It aims to help focus development on the delivery of prioritised, verifiable business value by providing a common vocabulary (also referred to as a UbiquitousLanguage) that spans the divide between Business and Technology.
BDD 使用幾乎近於自然語言的方式描述了軟體的行為過程,因此,可以直接作為軟體的需求文檔,也可以直接應用到測試中,作為測試的標準文檔。我們在做單元測試 時,經常是針對某個函數,或是某個類進行測試,但是被測函數或是被測的類是可能經常變化的,我們的測試案例也需要經常性的隨之變化。然後,BDD描述的是軟體的整個系統行為,幾近於需求文檔,可變性大大減小。因此,測試案例不需要做太大變化。同時,這樣的測試案例最貼近於需求,貼近於實際的系統行為。
BDD描述的行為就像一個個的故事(Story),系統業務專家、開發人員、測試人員一起合作,分析軟體的需求,然後將這些需求寫成一個個的故事。開發人員負責填充這些故事的內容,測試者負責檢驗這些故事的結果。通常,會使用一個故事的模板來對故事進行描述:As a [X]
I want [Y]
so that [Z]
同樣的一個故事,可能會有不同的情境。通過上面的模板描述了故事之後,再通過下面的模板對不同情境進行描述:Given some initial context (the givens),
When an event occurs,
then ensure some outcomes.
一個經典的例子就是ATM取款機的例子。故事的描述為:Title: Customer withdraws cash
As a customer,
I want to withdraw cash from an ATM,
so that I don’t have to wait in line at the bank.
作為一個客戶,我去ATM取錢,就不需要去排隊。同樣的故事,會有不同的情境發生:Scenario 1: Account is in credit
Given the account is in credit
And the card is valid
And the dispenser contains cash
When the customer requests cash
Then ensure the account is debited
And ensure cash is dispensed
And ensure the card is returned
如果你取款的金額比你的存款還多,將是下面的情境:Scenario 2: Account is overdrawn past the overdraft limit
Given the account is overdrawn
And the card is valid
When the customer requests cash
Then ensure a rejection message is displayed
And ensure cash is not dispensed
And ensure the card is returned
有了這樣的故事、情境的描述,測試者可以通過一些BDD的測試架構將上面的故事轉成測試代碼(當然,也可直接由開發來完成,這裡說測試者是為了方便理解),開發人員實現產品代碼,並保證測試代碼通過。
常見的BDD架構
BOO - Specter: A tool written for the Boo language, a .Net and Mono programming language.
C - CSpec
C++ - CppSpec Spec-CPP
C# .Net - NSpec
.Net - NBehave
.Net - NSpecify (incomplete site)
Delphi - dSpec
Groovy - GSpec, http://easyb.org easyb, tspec a non-English BDD framework with Thai syntax.
Java - JBehave, JDave, beanSpec, Instinct
Javascript - JSSpec
PHP - PHPSpec
Python - Specipy, spec plugin for nose
Ruby - RSpec, Shoulda, test-spec & bacon, Cucumber
Scala - Specs
回到正題
剛才就像對一門新的技術或方向進行了瞭解,再回過頭來想想,BDD對於測試的意義到底在哪。通過上面的瞭解,我們知道了行為驅動開發並不止是一個測試行為,其實很大意義上是一個產品需求分析人員、開發、測試共同來完成的一個行為。BDD同時也是一個非常理想化的過程,幾乎現在大部分的公司連TDD都實現不 了。作為測試人員,我們不是要去抱怨,我們應該慶幸作為測試開發人員,我們有機會使用最前沿,最先進的技術去解決問題。開發不能做到TDD,我們可以對自己的測試代碼實施TDD,我們可以嘗試各種不同的語言,然後選擇最優雅的方式去實現。同樣的,我們可以使用BDD所使用的自然語言描述方法來編寫我們的測試案例。這其實才是我本文的關鍵所在。
使用行為驅動開發,還需要打破傳統的魄力。因為之前幾乎沒有人會告訴你一個函數的命名為ShouldXXX(),也不會有有When, Then,And之類的類或函數。當你習慣它,會變得非常好玩。
使用行為驅動開發,可以使我們的案例描述邏輯更加清晰,可以想象以後維護你的代碼的人會對你大讚一番,因為省去了他理解代碼的大部分時間。
使用行為驅動開發,可以建立自動化測試與手工系統測試的橋樑。或許可以形成這樣的模式:手工測試人員負責設計故事和情境,自動化測試人員負責實現故事和情境。通過這樣的聯絡,手工測試人員能夠更好的瞭解到自動化測試所做的內容,從而免去不必要的重複勞動。
使用行為驅動開發,可以使你的測試更加貼近實際的使用者行為,從而找到系統的問題所在。
沒有銀彈
《沒有銀彈》(No Silver Bullet)是IBM大型電腦之父佛瑞德·布魯克斯(Fred Brooks)在1987年所發表的一篇關於軟體工程的 經典論文。該論述中強調由於軟體的複雜性本質,而使真正的銀彈並不存在;所謂的沒有銀彈是指沒有任何一項技術或方法可使軟體工程的生產力在十年內提高十 倍。剛才提到的使用行為驅動開發的種種好處,但是,如果運用不當,往往會適得其反。我們使用When,Then之類的連詞作為函數,並不是說可以隨意根據 需要隨意的命名。比如,使用This或These作為函數名,其行為就應該是返回一個或多個對象,而不是做其他的事情。可以想象,如果一個函數的名稱是 These,裡面執行的確實一個檔案拷貝的操作,對於代碼的維護者來說是多麼糟糕的一件事情啊。
其實我對行為驅動開發也只是初略的瞭解,本文表達的是僅僅是我個人的一些思想,如有錯誤之處也希望大家能夠指出。最後,我希望達到的是:
優美的測試代碼,就是一個個優美的故事。
本文參考資料:
http://behaviour-driven.org/
http://dannorth.net/introducing-bdd
http://www.javaworld.com/javaworld/jw-09-2008/jw-09-easyb.html