http://www.51testing.com/html/53/n-3710153.html
引言
“我希望這里能這樣……”,“我希望這里能再增加點東西……”——在軟件開發(fā)的世界,我們永遠(yuǎn)無法解決的一大難題,是客戶紛繁復(fù)雜并且不斷變化的需求。如何把需求映射為最終的軟件交付,是每一個軟件開發(fā)方法都無法回避的核心問題。
領(lǐng)域驅(qū)動設(shè)計(Domain-Driven Design)通過專注領(lǐng)域核心、建立通用語言等手段,以及聚合、倉儲等戰(zhàn)術(shù)模式,很好地達(dá)到了去偽存真、化繁為簡的目的,從而在團(tuán)隊協(xié)作、劃分邊界、建立模型等方面呈現(xiàn)出足夠的優(yōu)勢。但是DDD的實踐應(yīng)用,非常依賴與客戶溝通的技巧以及對領(lǐng)域知識的掌握。而這兩點,都是需要一些實踐經(jīng)驗積淀的,所以初入DDD并不容易。
實例化需求(Specification by Example,以下簡稱S&E),是我在學(xué)習(xí)BDD的過程中接觸到的開發(fā)方法。之所以稱其為開發(fā)方法,是因為它無法解決如何分析建模的問題,而主要回答了如何梳理用戶需求Specification,并最終將其實現(xiàn)為軟件交付Delivery的整個過程。其擅長的,是捕獲需求、確定驗收標(biāo)準(zhǔn)。
那么,為什么我要把DDD與S&E相提并論呢?這是因為,DDD能幫助我們劃分討論的上下文、提取通用語言UL、建立軟件模型,S&E則可以幫助我們梳理討論的場景、驗證模型能否達(dá)到交付要求、生成開發(fā)的相關(guān)文檔。所以我個人認(rèn)為,這兩種方法能形成優(yōu)勢互補(bǔ),幫助我們更容易地開發(fā)出“正確的軟件”。
關(guān)于書籍
DDD之父Eric Evans的《Domain-Driven Design: Tackling Complexity in the Heart of Software》、Vaughn Vernon的《Implementing Domain-Driven Design》、Scott Millett的《Patterns, Principles, and Practices of Domain-Driven Design》,還有Jimmy Nilsson的《Applying Domain-Driven Design and Patterns: With Examples in C# and .NET》為我們提供了完整的理論和具體的實踐。
現(xiàn)在通過我的書摘,再看看Gojko Adzic的《Specification by Example: How Successful Teams Deliver the Right Software 》是怎么通過正反事例的對比,來逐步闡述Specification by Example這一方法的吧。
注:《Specification by Example》一書已有中譯本《實例化需求:團(tuán)隊如何交付正確的軟件》,由人民郵電出版社出版。不過我手里只有這漿糊一樣排版的英文原版。所以,下文完全出自于我的個人理解,各種類比和小結(jié)將不斷穿插其中。
S&E過程概覽
軟件開發(fā)的壓力主要來源于:時間——開發(fā)的期限越來越短,成本——維護(hù)的要求越來越高,變化——需求改變的頻率越來越高。S&E采用一系列彼此銜接的處理模式及其產(chǎn)出的工件(artifact),幫助我們順利實現(xiàn)需求。其過程主要包括以下環(huán)節(jié),并作為本篇各小節(jié)的目錄:
· 根據(jù)業(yè)務(wù)目標(biāo)Business Goal,劃定問題域Scope
· 通過與客戶的溝通協(xié)作,制定需求Specification
· 用具體的場景事例Example,闡明需求Specification的具體內(nèi)容
· 提煉需求Specification,確定關(guān)鍵事例Key Example
· 在不修改需求的前提下,用自動化測試Automation來驗證需求
· 在重構(gòu)需求的同時,在系統(tǒng)與Executable Specification之間頻繁地進(jìn)行同步驗證
· 利用工具,提取組織良好的、易于尋找的、前后一致的活文檔Living Documentation
S&E關(guān)鍵過程示意圖

建立“以文檔為中心”的理念
目前實例化需求的過程現(xiàn)在有兩種流行的模型:以驗收測試為中心的ATDD,側(cè)重于自動化測試,優(yōu)點在于使開發(fā)目標(biāo)更明確,并防止功能退化;以系統(tǒng)行為規(guī)范為主導(dǎo)的BDD,側(cè)重于制定系統(tǒng)行為的場景,在客戶與開發(fā)團(tuán)隊之間建立共識。這兩種模型,各有長處、各有用途,所以無所謂孰優(yōu)孰劣。我們關(guān)注的,是它們生成的活文檔,這是實例化需求產(chǎn)出的最好工件。書的第三章,率先回答了什么是活文檔的問題,并倡導(dǎo)建立“以文檔為中心”的理念。
為什么需要活文檔?
維護(hù)開發(fā)文檔總是一件費力不討好的事情,卻又總是不得已而為之,因為將來的重構(gòu)與維護(hù)都需要以這樣的一份文檔為基礎(chǔ)。這份文檔需要能快速地勾勒出系統(tǒng)的輪廓,清晰地表達(dá)出系統(tǒng)主要的概念,準(zhǔn)確地描述系統(tǒng)架構(gòu)和模型的結(jié)構(gòu)。最關(guān)鍵的,這份文檔必須是最新的。所以,這份文檔不僅要完整,還必須是“鮮活的”。
測試為什么可以作為文檔?
自動化測試本身是按一定的邏輯編排的,所以具有一定的組織結(jié)構(gòu)性。測試方法的名稱,也可以看作是測試的一種“自描述”文本。所以這種結(jié)構(gòu)性與自描述性,與開發(fā)文檔的需求不謀而合,所以測試可以被當(dāng)作文檔的一種形式——“代碼即文檔”。
但是要注意,不能因此偏重于測試本身,而忽略了測試與需求之間的聯(lián)系,使得測試變得臃腫和不易修改。ATDD的方法,往往過于注重編寫和執(zhí)行測試,因此容易寫出不易維護(hù)的測試,導(dǎo)致有需求變化時,產(chǎn)生牽一發(fā)而動全身式的連鎖反應(yīng),大量的維護(hù)與重構(gòu)工作使得先前的測試工作變得得不償失,因此要極力避免。
如何從測試得到活文檔?
如果把帶有Example的Executable Specification比喻為頁面,那么整個活文檔就是由此構(gòu)成的一整本書。利用Relish等BDD工具,我們可以把通過自動化測試驗證后的Specification提取為HTML或者PDF等格式的文檔系統(tǒng),這甚至可以稍加修改就作為用戶手冊使用。
綜上所述,因為重構(gòu)與維護(hù)的難度,我們需要一份組織良好的文檔。BDD的自動化測試正符合文檔的要求,而且恰好這種文檔可以利用一些BDD工具從可以執(zhí)行的Specification中提取出來,所以實例化需求方法可以視作一種建立在“以文檔為中心”理念上的開發(fā)方法。
根據(jù)業(yè)務(wù)目標(biāo)劃定問題域
敏捷開發(fā)是以用戶故事為核心的,所以故事講得好不好至關(guān)重要。那么這個講故事的責(zé)任究竟應(yīng)由誰來承擔(dān)?傳統(tǒng)的軟件開發(fā),認(rèn)為劃分問題域、講清故事是客戶的事。對此,《BDD in Action》和本書的兩位作者都反復(fù)強(qiáng)調(diào),『不能交由客戶去編寫用戶故事、用例清單等細(xì)節(jié),否則就等同于讓客戶去提供一個具體的、高層次的解決方案了?!凰裕瑒澐謫栴}域、講好用戶故事,是開發(fā)團(tuán)隊的責(zé)任。在劃定問題域這個環(huán)節(jié),重點應(yīng)該是引導(dǎo)用戶弄清究竟需要什么,進(jìn)而通過發(fā)掘現(xiàn)有業(yè)務(wù)的潛在,提出新的思路和新的方案。
使用Impact Mapping
劃定問題域的具體方法,是理解“Why”與“Who”。這和我在前篇對結(jié)合BDD進(jìn)行DDD開發(fā)的一點思考和整理中介紹Impact Mapping這個工具時一樣,重點是理解“為什么要這樣做?”、“誰人將從中受益?”等問題,弄清客戶開發(fā)系統(tǒng)所能期望的價值究竟是從何而來。此時,由于系統(tǒng)輪廓不清不楚,可能會感覺無從下手。為此,建議先分清業(yè)務(wù)目標(biāo)與要交付的功能,而不是嘗試去把每一個用戶故事描述清楚。對此,我個人認(rèn)為Impact Mapping提供的Goal-Actor-Impact-Deliverable模型,將是一個非常合適的挖掘工具。我們可以通過連續(xù)的Why提問,來弄清真實的、具體的、有期限的、能度量的業(yè)務(wù)目標(biāo)。

從客戶期待的輸出結(jié)果推導(dǎo)業(yè)務(wù)目標(biāo)
當(dāng)難以確定業(yè)務(wù)目標(biāo)時,先不要急于討論需要哪些功能,而是可以從描述客戶期待的輸出入手,分析為什么需要這樣的輸出,從而歸納出業(yè)務(wù)目標(biāo)所在。比如對于一個ERP系統(tǒng),堅持“Report-first”,用各種報表展示客戶期待的系統(tǒng)輸出結(jié)果,由此發(fā)掘業(yè)務(wù)目標(biāo),可以幫助我們把注意力集中在具體的報表項內(nèi)容上,而暫時把流程、處理等功能性需求放在一邊。
使用“As a - In order to - I want”描述目標(biāo)
As a stakeholder
In order to achive something valuable
I want some system function
這樣三段式的描述,可以與Impact Mapping的內(nèi)容有機(jī)地聯(lián)系起來,相當(dāng)于根據(jù)Actor-Impact-Deliverable直接轉(zhuǎn)譯而來。

詢問的技巧
為什么這東西有用? 通過提問,引導(dǎo)客戶用具體的事例,來回答為什么某個功能有用?是如何給他的業(yè)務(wù)帶來幫助的?“為什么需要這些東西”帶有詰問的口氣,因此并不推薦。
有什么可替代的方案? 通過尋找可以替代方案,可以幫助客戶從另一個角度去思考和認(rèn)識自己的業(yè)務(wù)目標(biāo),同時也給團(tuán)隊的實現(xiàn)提供新的思路、決定當(dāng)前提議的是否已經(jīng)是最佳方案。
通過溝通協(xié)作來制定需求
系統(tǒng)需求,需要由客戶與開發(fā)團(tuán)隊達(dá)成一致,確保系統(tǒng)的各個方面的功能都被包括其中,并有明確具體的驗收指征作為約束。這和DDD中分享消化業(yè)務(wù)知識,得到領(lǐng)域的通用語言是一致的。在DDD中,也提倡專注于最有意思的對話上,并從用例開始,從一個系統(tǒng)行為作為起點,組織開發(fā)人員、業(yè)務(wù)人員和業(yè)務(wù)專家,圍繞一個特定的場景進(jìn)行討論,由此發(fā)現(xiàn)這一場景內(nèi)的領(lǐng)域概念和業(yè)務(wù)知識。這一點也正是我非常珍視的,實例化需求和BDD這一類方法,楔入DDD的關(guān)鍵點。
這種協(xié)作,不僅發(fā)生在開發(fā)人員與客戶之間,同樣也在開發(fā)人員與測試人員之間。如果開發(fā)人員與測試人員沒有圍繞Specification達(dá)成一致,那么雙方就會各行其是。開發(fā)人員看到的是一堆的需求,而測試人員看到的是一堆的測試用例。若由開發(fā)人員撰寫Specification,它會因為過于貼近模型設(shè)計而充斥大量的模式、架構(gòu)元素,從而變得難以理解。若改由測試人員獨立撰寫時,可能又會因為太過瑣碎零散而變得難以維護(hù),最終迷失在各種測試細(xì)節(jié)的汪洋大海之中。測試人員編寫的測試,沒辦法幫助開發(fā)人員去組織整個系統(tǒng)的各個部分,也無法通過自動化測試驅(qū)動整個開發(fā)過程。測試人員編寫的測試,也沒法被當(dāng)作Specification再被開發(fā)人員利用,因為這些測試都是站在測試人員的立場,用測試人員的方言、專業(yè)術(shù)語編寫和描述的,所以沒辦法用于雙方的溝通。對于測試人員,則會在每次系統(tǒng)需求改動時,面對一大堆的測試重構(gòu)。因為這些測試都不支持自動化測試,或者不容易被其他人理解。所以,協(xié)作是廣泛的、多重的、具體的。
視協(xié)作的規(guī)模不同,可以分為:
· 大型的全體工坊: 適合項目剛開始的階段,增進(jìn)彼此的了解,并劃定足夠大的范圍。但是要協(xié)調(diào)如此多人員的日程安排在某一天達(dá)到一致,是一件非常困難的事。
· “三劍客”式的小型工坊: 開發(fā)人員、測試人員、業(yè)務(wù)人員組成的小型團(tuán)隊,主要負(fù)責(zé)勾勒具體場景,產(chǎn)出Given-When-Then三段式的Feature文件。
· 結(jié)對編程: 分析人員與開發(fā)人員的結(jié)對,這是一種高效的方式。為了避免開發(fā)人員站在自己的視角采用TDD的方法編寫用戶故事而有失偏頗,所以轉(zhuǎn)由分析人員編寫測試,好讓分析人員掌控Specification的全貌。但這又會產(chǎn)生另一個問題,分析人員編寫的故事可能會影響到許多已有的測試,而他自己根本無法預(yù)見。同時,分析人員習(xí)慣于一個故事對應(yīng)一個流程,從而產(chǎn)生大量重復(fù)。所以最后可行的方案,是由分析人員制定測試計劃,并與開發(fā)人員一起編寫feature文件,防止遺漏可能的需求。
· 非正式會議: 由分析人員、編程人員、測試人員和業(yè)務(wù)相關(guān)人員采取非正式的聚會形式,目的在于統(tǒng)一理解、消化知識,發(fā)現(xiàn)在各自獨立工作時未能發(fā)現(xiàn)的內(nèi)容與細(xì)節(jié)。
用事例闡明需求的具體內(nèi)容
只有當(dāng)場景描述具有很強(qiáng)的帶入感時,才能激發(fā)客戶參與討論的熱情,才更容易達(dá)成共識,并發(fā)掘潛在的概念和需求。傳統(tǒng)的基于平面文檔的平鋪直敘的方式,在向用戶展示系統(tǒng)場景、捕捉系統(tǒng)需求時,可能會因為詞不達(dá)意,而導(dǎo)致不同的人產(chǎn)生不同的理解,所以描述性的文檔始終無法與清晰的代碼媲美、也遠(yuǎn)沒有代碼直接。然而清晰的代碼并非一朝一夕,讓用戶直接面對系統(tǒng)代碼也完全沒有意義,所以我們退而求其次,改用一個特定場景下的具體事例來表述系統(tǒng)的行為,達(dá)到與客戶有效交流的目的。所以,舉例說明的方式,對于共同認(rèn)識和理解某個場景是非常有益的。
在選擇和描述每一個例子時,作者提出要堅持“例子四原則”:
· 例子總是明確的。
要使用"yes or no"這樣的問卷調(diào)查,應(yīng)更注意彼此的溝通。
不要使用“小于10”這樣的『比較性』描述,而使用“9”、“11”這樣明確具體的值,來對應(yīng)不同條件下的場景。因為比較性的描述,總是意味著一個值域,而不是單個的值。相比引起變化的單個臨界值,這樣的值域?qū)τ谖覀兝斫馐吕]有更多的幫助。
· 例子總是完整的。
要用具體的數(shù)值去表述不同條件下的不同場景,特別是要注意正反兩方面的數(shù)據(jù)輸入組合。對負(fù)數(shù)、0這樣的邊界值給予足夠重視。當(dāng)涉及的條件是對象時,則要留意無效的引用、null等。
使用替代的方法進(jìn)行驗證。這個小節(jié)在原書的描述里非?;逎?。我大意理解為,對于一些新舊數(shù)據(jù)并存的系統(tǒng),我們很容易只惦記著新環(huán)境下的各種例子,而遺忘了舊數(shù)據(jù)也應(yīng)該當(dāng)被考慮在內(nèi)。
· 例子總是現(xiàn)實的。
建議直接使用真實的數(shù)據(jù),而不用費心為測試專門編造數(shù)據(jù)。這樣可以充分利用舊數(shù)據(jù),減少未來可能的數(shù)據(jù)兼容風(fēng)險,保證新舊數(shù)據(jù)在系統(tǒng)中的一致性。
直接由用戶提供基礎(chǔ)的例子,而不要自己臆造。
· 例子總是易于理解的。
不要拿一堆的參數(shù)組合表格給客戶,我們重點關(guān)注的應(yīng)當(dāng)是所有的臨界點。對每一個臨界條件,都應(yīng)當(dāng)進(jìn)行認(rèn)真討論。如果雙方對該條件是否確系臨界條件有爭議,那說明雙方對例子的理解本身就是有誤解的。這似乎又回到所有問題都要達(dá)成共識的這個原點上來了。
注意尋找潛在的概念。在面對與一個功能聯(lián)系的一大摞事例時,解決事例過于細(xì)碎、不易理解的方法,在于對事例適當(dāng)進(jìn)行抽象和歸納,然后再轉(zhuǎn)頭分析此前那些瑣碎的下層概念,如同是切碎了再進(jìn)行二次理解,這樣可能會發(fā)現(xiàn)一些潛在的概念。
在安全、性能等非功能性的需求方面, 當(dāng)其重要性已經(jīng)達(dá)到影響業(yè)務(wù)價值或者業(yè)務(wù)目標(biāo)實現(xiàn)程度的時候,那就清晰地表達(dá)出來。這與《UML精粹》中的觀點是一致的,一切需求的重要性視其對實現(xiàn)業(yè)務(wù)價值、業(yè)務(wù)目標(biāo)的影響程度而定。在性能、響應(yīng)時間這些無法準(zhǔn)確表述的需求方面,作者引入了QUPER模型。對這個模型,我個人理解是預(yù)估這些指標(biāo)對應(yīng)的障礙,以及由此產(chǎn)生的開銷,然后再展開討論。每個問題會被分成三個方面:
可用性:是否有功用性——“能不能用?”
分化性:是否有市場占有能力——“相比其他產(chǎn)品是不是更具優(yōu)勢?”
飽和性:過度設(shè)計沒有意義——“弄得再好一些有沒有實際意義?”
作者使用了“手機(jī)開機(jī)速度”作為例子:不能開機(jī),手機(jī)就沒用;開機(jī)速度太慢,就沒市場競爭力;開機(jī)非??炝耍倏炀蜎]有意義了。
提煉需求
好記性不如爛筆頭。交流的結(jié)果一定要以某種形式記載下來。原始的例子就象未經(jīng)雕琢的鉆石,只有提煉后才是關(guān)鍵的、易理解的、方便轉(zhuǎn)換為可執(zhí)行Specification的、能予以自動化測試的Key Example。
Specification應(yīng)該是明確的、可測試的
這一點,和Example的”明確的”是同樣的含義。要盡可能消除描述上的模棱兩可,并且要保證所有參與討論人員認(rèn)識上的一致。
Specification應(yīng)當(dāng)是真實的互動,而不是簡單的腳本
腳本通常更側(cè)重于描述一個事物是如何變化的,更多的傾向于流程方面的內(nèi)容。這也是客戶在描述的時候,容易掉入的一個陷阱——“先這樣,然后那樣,接著再怎么樣,最后又是什么樣”。這種腳本或者說是流程形式的表述,缺乏一個系統(tǒng)的視角,缺少對系統(tǒng)與用戶交互情況的表達(dá),而且流程本身很容易改變并且難以維護(hù),所以并不適合直接作為Specification。正確的方式,應(yīng)該按”在這樣的情況下,系統(tǒng)會做出那樣的反應(yīng)“的形式進(jìn)行表述。重點是”系統(tǒng)應(yīng)該做什么“,而不是”系統(tǒng)應(yīng)如何工作”。
Specification應(yīng)該是業(yè)務(wù)功能相關(guān)的,而不僅僅是軟件設(shè)計意義上的結(jié)果
Specification不要與代碼、與UI等技術(shù)實現(xiàn)細(xì)節(jié)耦合太緊。技術(shù)層面的難題,以及流程等細(xì)節(jié),留待再下層的自動化測試去解決。
Specification應(yīng)該是自解釋的、不言自明的
為了提高Specification的可閱讀性,可以給它增添一段描述文本,然后交給其他人看,靜靜地觀察對方的反應(yīng)。如果對方無需額外提問就能理解并達(dá)成一致,那說明這個Specification符合預(yù)期。否則就把回答對方的解釋,也寫進(jìn)開頭的這段描述性文本里。
在篩選關(guān)鍵事例時,應(yīng)優(yōu)先把握所有成功的場景,而把可能失敗的情景先放在一邊,由簡入繁地先把功能正常地展現(xiàn)出來。在具體篩選時,可以從以下幾個方面著手:
描述了業(yè)務(wù)功能的某個方面
描述了重要的業(yè)務(wù)邊界或者業(yè)務(wù)規(guī)則
描述了可能導(dǎo)致失敗的某種情形
Specification應(yīng)該是專注的
使用Given-When-Then三段式表述Specification,并且盡量避免考慮動作或者事件之間的依賴關(guān)系,最好就專注于一個動作、一個事件。對于新舊數(shù)據(jù)共存的系統(tǒng),比如ES+CQRS里新舊版本的領(lǐng)域事件,為了保持專注,建議把這種兼容性問題壓入自動化測試層去解決。對于系統(tǒng)當(dāng)中的缺省值,雖然能讓Specification更易讀,但考慮到這種缺省值如果表述在Specification中,將會導(dǎo)致過強(qiáng)的依賴性,所以也建議移入自動化測試層。在這個問題上,可以參考“魔數(shù)”。當(dāng)我們把魔數(shù)顯式地定義出來時,才更有助于我們消除誤解。將其移動到自動化測試層,或者放入一個全局的配置當(dāng)中,都是相對更靈活的方法。
Specification應(yīng)該是具備領(lǐng)域意義的
這一點又轉(zhuǎn)回到DDD了,即Specification中引用的概念、關(guān)系,都應(yīng)該與當(dāng)前上下文中的通用語言保持一致。
用自動化測試驗證需求
隨著軟件規(guī)模的逐漸增長,測試的數(shù)量、大小、應(yīng)對變化的能力,都要求我們采取自動化的測試方式。在這個過程中,必須以預(yù)定的Specification不再修改作為前提,否則我們的自動化測試只能是以訛傳訛了。換個角度看,雖然自動化測試增加了學(xué)習(xí)的成本,要引入額外的BDD工具,編寫額外的Feature與Specification,得到的卻是前后一致的需求表達(dá)和更加輕松的后期維護(hù),代碼的實現(xiàn)也會更自然。這一點,我認(rèn)為和DDD里的從UL到代碼的展開是一致的。因為有統(tǒng)一的UL和清晰的模型,所以直接映射到代碼也就更直觀和自然了。
在具體實施自動化測試時,應(yīng)該由簡入繁、從易到難,事先做好規(guī)劃。因為構(gòu)建整個自動化測試的上下文環(huán)境是相對比較耗時費力的,這個上下文還要集成到一定的系統(tǒng)環(huán)境中才能執(zhí)行,將來需求發(fā)生變化時這個環(huán)境也能被重用,所以非常有必要在對待整個測試環(huán)境規(guī)劃時更慎重一點。
在具體的實現(xiàn)環(huán)節(jié),將自動化測試與編寫業(yè)務(wù)代碼同步,比如采用TDD的方法,能讓所有人把精力集中在測試上,保證Specification順利實現(xiàn)。這就如同公交車,如果中途沒有乘客上下,自然會跑得很快。但事實并非如此,測試的職責(zé)就是保證Specification的實現(xiàn)、業(yè)務(wù)代碼的正確,所以自動化測試的規(guī)劃與實現(xiàn),必須要由開發(fā)團(tuán)隊承擔(dān)起來,不能交給其他人。
手動測試與自動化測試的區(qū)別在于,手動測試側(cè)重于準(zhǔn)備上下文,然后測試是否通過,關(guān)注的是成功與否;自動化測試則關(guān)心導(dǎo)致測試失敗的原因。特別的,手動測試通常是腳本化的,一個步驟緊跟一個步驟,每一次測試都重復(fù)這個過程。如果測試失敗,那么手動檢查其中的每個步驟也在情理之中,反正都是手動的。自動化測試則必須消除這種測試步驟之間的依賴性,否則當(dāng)測試失敗時,無法確定究竟是哪個步驟出了問題,自動化測試將因此退化成手動測試。如果遇到這種情況,可以將其切分為若干個小的測試,比如每個步驟對應(yīng)一個小的自動化測試,改由測試上下文通過準(zhǔn)備測試條件將這些小測試聯(lián)系起來。由此引申出一個問題:測試代碼要不要良好的組織與設(shè)計?答案是肯定的。因為良好編碼的測試代碼,才能方便維護(hù)和閱讀,并作為活文檔的提煉來源。
Executable Specification通常是用文本或者HTML格式進(jìn)行描述的(想想Cucumber的Step或者Spock里的測試方法的描述式命名)。這樣當(dāng)這個可以執(zhí)行的需求說明發(fā)生改變時,通常不需要重新編譯業(yè)務(wù)代碼。而自動化測試是代碼,并負(fù)責(zé)對Specification的驗證,所以當(dāng)需求說明改變時,要重新編譯。此時,為了避免在Specification中混雜太多計算、判斷的邏輯,要把這些驗證邏輯放在自動化測試?yán)铮灰硎鲈赟pecification里。因為對于Specification而言,在轉(zhuǎn)換為Executable Specification時應(yīng)當(dāng)關(guān)注的是“測試什么”,而把“如何測試”的責(zé)任交給自動化測試。
盡管測試不能是腳本,容易變化的流程應(yīng)盡量放在自動化測試層。但是無論怎樣,業(yè)務(wù)流程總是客觀存在的,通過When-Then的事件驅(qū)動方式,我們可以借由若干個Specification的組合,展示完整的業(yè)務(wù)流程。而具體的業(yè)務(wù)邏輯,則放在Specification里。所以,不要在測試代碼里重復(fù)業(yè)務(wù)流程或業(yè)務(wù)邏輯。
UI的自動化測試
依賴UI與數(shù)據(jù)庫的測試,是自動化測試面臨的最大困難。書里提到了許多建議,但都非常需要實踐進(jìn)行驗證,才能深刻領(lǐng)會。至少我只理解了皮毛,所以這一段的內(nèi)容暫時沒有辦法總結(jié)。盡管如此,大量地引入Stub與Mock進(jìn)行測試、隔離UI與業(yè)務(wù)模型、進(jìn)行持久化無關(guān)的設(shè)計、建立統(tǒng)一的應(yīng)用服務(wù)層、在Specification里竭力避免引入UI與存儲相關(guān)的元素等等,都是可行的方案,類似MVC、MVP、MVVM等模式也將成為我們解耦的利器。
即使要對UI進(jìn)行自動化測試,也建議使用針對UI編寫的Specification進(jìn)行驗證,而且不要使用“錄制-回放”工具。因為這類工具生成的腳本通常會增加一定的學(xué)習(xí)成本,而且會非常難以理解,不便于維護(hù)。
從需求說明到UI的自動化測試,可以從以下3個抽象力度逐漸弱化的不同層次進(jìn)行實現(xiàn)。其中,需求說明應(yīng)該在第一個業(yè)務(wù)規(guī)則層中描述,自動化層則應(yīng)該通過組合第三個技術(shù)行為層來表達(dá)第二個業(yè)務(wù)工作流層。這樣分層實現(xiàn)的從需求到測試的描述,更易于理解,也更高效。
業(yè)務(wù)規(guī)則層 :測試要展示或者操作的是什么? 對一條業(yè)務(wù)規(guī)則進(jìn)行描述:對購買了5本書的客戶提供包郵服務(wù)。
業(yè)務(wù)流程層 :如何通過UI使用某個功能,以更高的抽象級別進(jìn)行描述? 對一個業(yè)務(wù)流程進(jìn)行描述:放5本書進(jìn)購物車,然后驗證是否提示已包郵。
技術(shù)行為層 :在一個流程的某個環(huán)節(jié),需要哪些技術(shù)性步驟?對實現(xiàn)一個業(yè)務(wù)流程的具體UI操作步驟進(jìn)行描述:點擊5本書的"放入購物車"按鈕,然后點擊“下單”,頁面顯示“已包郵”。
持久層的自動化測試
用數(shù)據(jù)庫作為自動化測試的數(shù)據(jù)來源,是一個比較便利的方式,但是如何管理這些數(shù)據(jù)卻成為一個難題。要避免直接使用舊存的數(shù)據(jù)作為測試的數(shù)據(jù)源,因為這種數(shù)據(jù)與現(xiàn)在的需求可能存在沖突、不易理解。對于構(gòu)造過程相對復(fù)雜的對象圖,可以嘗試在數(shù)據(jù)庫里預(yù)置相關(guān)數(shù)據(jù),以提高自動化測試的速度。
在重構(gòu)需求的同時,頻繁地進(jìn)行同步驗證
這一部分的內(nèi)容,與重構(gòu)、持續(xù)集成緊密相關(guān),因此提到的也多是化整為零、“不要想一口吃成一個胖子”、用Mock隔離故障點、以事件驅(qū)動測試、先保證同步再嘗試異步測試等等建議,并且提倡引入并發(fā)測試、快慢分組等方式盡快提高測試的反饋速度。
“業(yè)務(wù)時鐘”
對于那種周期性執(zhí)行的測試,引入自然時鐘顯然是不合適的,所以新增“業(yè)務(wù)時鐘”的概念去控制這個周期,使之可以根據(jù)測試要求隨時執(zhí)行這一類的測試。我的理解,它等同于一個虛擬的時鐘,基本原理還是觸發(fā)一個時間事件,然后利用這個事件去驅(qū)動測試。
提取活文檔
由于提取活文檔更多的是BDD工具的使用,所以只要有編寫優(yōu)雅的Specification和自動化測試作為基礎(chǔ),文檔的生成是一件水到渠成的事。在這個部分,主要的建議包括:
以UI導(dǎo)航流程、功能結(jié)構(gòu)、業(yè)務(wù)過程等組織文檔內(nèi)容。
即便是虛擬的角色,也要保證完整的角色信息。
合理使用Tag、WiKi等一些文檔組織技術(shù),提高可閱讀性。
在文檔中始終保證領(lǐng)域?qū)S谜Z言DSL的統(tǒng)一。
寫在最后
實例化需求的實踐,重點和難點都在其中的4個環(huán)節(jié):制定需求、描述需求、提煉需求和自動化測試。而《實例化需求》這本書本身的內(nèi)容,也更偏重于用正反兩方面的具體事例來引導(dǎo)我們的思考,涉及具體操作步驟的內(nèi)容相對較少。所以和DDD一樣,掌握實例化需求的方法,也需要大量的實操和經(jīng)驗積累。整理出這篇書摘,附上自己閱讀時的注解,希望可以拋磚引玉、溫故而知新。