對實例化需求方法的整理與思考

引言

「我但願這裏能這樣……」,「我但願這裏能再增長點東西……」——在軟件開發的世界,咱們永遠沒法解決的一大難題,是客戶紛繁複雜而且不斷變化的需求。如何把需求映射爲最終的軟件交付,是每個軟件開發方法都沒法迴避的核心問題。html

領域驅動設計(Domain-Driven Design)經過專一領域核心、創建通用語言等手段,以及聚合、倉儲等戰術模式,很好地達到了去僞存真、化繁爲簡的目的,從而在團隊協做、劃分邊界、創建模型等方面呈現出足夠的優點。可是DDD的實踐應用,很是依賴與客戶溝通的技巧以及對領域知識的掌握。而這兩點,都是須要一些實踐經驗積澱的,因此初入DDD並不容易。數據庫

實例化需求(Specification by Example,如下簡稱S&E),是我在學習BDD的過程當中接觸到的開發方法。之因此稱其爲開發方法,是由於它沒法解決如何分析建模的問題,而主要回答瞭如何梳理用戶需求Specification,並最終將其實現爲軟件交付Delivery的整個過程。其擅長的,是捕獲需求、肯定驗收標準。編程

那麼,爲何我要把DDD與S&E相提並論呢?這是由於,DDD能幫助咱們劃分討論的上下文、提取通用語言UL、創建軟件模型,S&E則能夠幫助咱們梳理討論的場景、驗證模型可否達到交付要求、生成開發的相關文檔。因此我我的認爲,這兩種方法能造成優點互補,幫助咱們更容易地開發出「正確的軟件」。數組

關於書籍

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》爲咱們提供了完整的理論和具體的實踐。安全

如今經過個人書摘,再看看
Gojko Adzic的《Specification by Example: How Successful Teams Deliver the Right Software 》是怎麼經過正反事例的對比,來逐步闡述Specification by Example這一方法的吧。架構

注:《Specification by Example》一書已有中譯本《實例化需求:團隊如何交付正確的軟件》,由人民郵電出版社出版。不過我手裏只有這漿糊同樣排版的英文原版。因此,下文徹底出自於個人我的理解,各類類比和小結將不斷穿插其中。併發

看到這排版,我真心醉了

S&E過程概覽

軟件開發的壓力主要來源於:時間——開發的期限愈來愈短,成本——維護的要求愈來愈高,變化——需求改變的頻率愈來愈高。S&E採用一系列彼此銜接的處理模式及其產出的工件(artifact),幫助咱們順利實現需求。其過程主要包括如下環節,並做爲本篇各小節的目錄:app

S&E關鍵過程示意圖

S&E的關鍵過程示意圖

創建「以文檔爲中心」的理念

目前實例化需求的過程如今有兩種流行的模型:以驗收測試爲中心的ATDD,側重於自動化測試,優勢在於使開發目標更明確,並防止功能退化;以系統行爲規範爲主導的BDD,側重於制定系統行爲的場景,在客戶與開發團隊之間創建共識。這兩種模型,各有長處、各有用途,因此無所謂孰優孰劣。咱們關注的,是它們生成的活文檔,這是實例化需求產出的最好工件。書的第三章,率先回答了什麼是活文檔的問題,並倡導創建「以文檔爲中心」的理念。異步

爲何須要活文檔?

維護開發文檔老是一件費力不討好的事情,卻又老是不得已而爲之,由於未來的重構與維護都須要以這樣的一份文檔爲基礎。這份文檔須要能快速地勾勒出系統的輪廓,清晰地表達出系統主要的概念,準確地描述系統架構和模型的結構。最關鍵的,這份文檔必須是最新的。因此,這份文檔不只要完整,還必須是「鮮活的」。工具

測試爲何能夠做爲文檔?

自動化測試自己是按必定的邏輯編排的,因此具備必定的組織結構性。測試方法的名稱,也能夠看做是測試的一種「自描述」文本。因此這種結構性與自描述性,與開發文檔的需求不謀而合,因此測試能夠被看成文檔的一種形式——「代碼即文檔」。

可是要注意,不能所以偏重於測試自己,而忽略了測試與需求之間的聯繫,使得測試變得臃腫和不易修改。ATDD的方法,每每過於注重編寫和執行測試,所以容易寫出不易維護的測試,致使有需求變化時,產生牽一髮而動全身式的連鎖反應,大量的維護與重構工做使得先前的測試工做變得得不償失,所以要極力避免。

如何從測試獲得活文檔?

若是把帶有Example的Executable Specification比喻爲頁面,那麼整個活文檔就是由此構成的一整本書。利用Relish等BDD工具,咱們能夠把經過自動化測試驗證後的Specification提取爲HTML或者PDF等格式的文檔系統,這甚至能夠稍加修改就做爲用戶手冊使用。

綜上所述,由於重構與維護的難度,咱們須要一份組織良好的文檔。BDD的自動化測試正符合文檔的要求,並且剛好這種文檔能夠利用一些BDD工具從能夠執行的Specification中提取出來,因此實例化需求方法能夠視做一種創建在「以文檔爲中心」理念上的開發方法。

根據業務目標劃定問題域

敏捷開發是以用戶故事爲核心的,因此故事講得好很差相當重要。那麼這個講故事的責任究竟應由誰來承擔?傳統的軟件開發,認爲劃分問題域、講清故事是客戶的事。對此,《BDD in Action》和本書的兩位做者都反覆強調,『不能交由客戶去編寫用戶故事、用例清單等細節,不然就等同於讓客戶去提供一個具體的、高層次的解決方案了。』因此,劃分問題域、講好用戶故事,是開發團隊的責任。在劃定問題域這個環節,重點應該是引導用戶弄清究竟須要什麼,進而經過發掘現有業務的潛在,提出新的思路和新的方案。

使用Impact Mapping

劃定問題域的具體方法,是理解「Why」與「Who」。這和我在前篇對結合BDD進行DDD開發的一點思考和整理中介紹Impact Mapping這個工具時同樣,重點是理解「爲何要這樣作?」、「誰人將從中受益?」等問題,弄清客戶開發系統所能指望的價值到底是從何而來。此時,因爲系統輪廓不清不楚,可能會感受無從下手。爲此,建議先分清業務目標與要交付的功能,而不是嘗試去把每個用戶故事描述清楚。對此,我我的認爲Impact Mapping提供的Goal-Actor-Impact-Deliverable模型,將是一個很是合適的挖掘工具。咱們能夠經過連續的Why提問,來弄清真實的、具體的、有期限的、能度量的業務目標。

Impact Mapping

從客戶期待的輸出結果推導業務目標

當難以肯定業務目標時,先不要急於討論須要哪些功能,而是能夠從描述客戶期待的輸出入手,分析爲何須要這樣的輸出,從而概括出業務目標所在。好比對於一個ERP系統,堅持「Report-first」,用各類報表展現客戶期待的系統輸出結果,由此發掘業務目標,能夠幫助咱們把注意力集中在具體的報表項內容上,而暫時把流程、處理等功能性需求放在一邊。

使用「As a - In order to - I want」描述目標

  • As a stakeholder
  • In order to achive something valuable
  • I want some system function

這樣三段式的描述,能夠與Impact Mapping的內容有機地聯繫起來,至關於根據Actor-Impact-Deliverable直接轉譯而來。

Melvin PerezCx之映射圖

詢問的技巧

  • 爲何這東西有用? 經過提問,引導客戶用具體的事例,來回答爲何某個功能有用?是如何給他的業務帶來幫助的?「爲何須要這些東西」帶有詰問的口氣,所以並不推薦。
  • 有什麼可替代的方案? 經過尋找能夠替代方案,能夠幫助客戶從另外一個角度去思考和認識本身的業務目標,同時也給團隊的實現提供新的思路、決定當前提議的是否已是最佳方案。

經過溝通協做來制定需求

系統需求,須要由客戶與開發團隊達成一致,確保系統的各個方面的功能都被包括其中,並有明確具體的驗收指徵做爲約束。這和DDD中分享消化業務知識,獲得領域的通用語言是一致的。在DDD中,也提倡專一於最有意思的對話上,並從用例開始,從一個系統行爲做爲起點,組織開發人員、業務人員和業務專家,圍繞一個特定的場景進行討論,由此發現這一場景內的領域概念和業務知識。這一點也正是我很是珍視的,實例化需求和BDD這一類方法,楔入DDD的關鍵點。

這種協做,不只發生在開發人員與客戶之間,一樣也在開發人員與測試人員之間。若是開發人員與測試人員沒有圍繞Specification達成一致,那麼雙方就會各行其是。開發人員看到的是一堆的需求,而測試人員看到的是一堆的測試用例。若由開發人員撰寫Specification,它會由於過於貼近模型設計而充斥大量的模式、架構元素,從而變得難以理解。若改由測試人員獨立撰寫時,可能又會由於太過瑣碎零散而變得難以維護,最終迷失在各類測試細節的汪洋大海之中。測試人員編寫的測試,沒辦法幫助開發人員去組織整個系統的各個部分,也沒法經過自動化測試驅動整個開發過程。測試人員編寫的測試,也無法被看成Specification再被開發人員利用,由於這些測試都是站在測試人員的立場,用測試人員的方言、專業術語編寫和描述的,因此沒辦法用於雙方的溝通。對於測試人員,則會在每次系統需求改動時,面對一大堆的測試重構。由於這些測試都不支持自動化測試,或者不容易被其餘人理解。因此,協做是普遍的、多重的、具體的。

視協做的規模不一樣,能夠分爲:

  • 大型的全體工坊: 適合項目剛開始的階段,增進彼此的瞭解,並劃定足夠大的範圍。可是要協調如此多人員的日程安排在某一天達到一致,是一件很是困難的事。
  • 「三劍客」式的小型工坊: 開發人員、測試人員、業務人員組成的小型團隊,主要負責勾勒具體場景,產出Given-When-Then三段式的Feature文件。
  • 結對編程: 分析人員與開發人員的結對,這是一種高效的方式。爲了不開發人員站在本身的視角採用TDD的方法編寫用戶故事而有失偏頗,因此轉由分析人員編寫測試,好讓分析人員掌控Specification的全貌。但這又會產生另外一個問題,分析人員編寫的故事可能會影響到許多已有的測試,而他本身根本沒法預見。同時,分析人員習慣於一個故事對應一個流程,從而產生大量重複。因此最後可行的方案,是由分析人員制定測試計劃,並與開發人員一塊兒編寫feature文件,防止遺漏可能的需求。
  • 非正式會議: 由分析人員、編程人員、測試人員和業務相關人員採起非正式的聚會形式,目的在於統一理解、消化知識,發如今各自獨立工做時未能發現的內容與細節。

用事例闡明需求的具體內容

只有當場景描述具備很強的帶入感時,才能激發客戶參與討論的熱情,才更容易達成共識,併發掘潛在的概念和需求。傳統的基於平面文檔的平鋪直敘的方式,在向用戶展現系統場景、捕捉系統需求時,可能會由於詞不達意,而致使不一樣的人產生不一樣的理解,因此描述性的文檔始終沒法與清晰的代碼媲美、也遠沒有代碼直接。然而清晰的代碼並不是一朝一夕,讓用戶直接面對系統代碼也徹底沒有意義,因此咱們退而求其次,改用一個特定場景下的具體事例來表述系統的行爲,達到與客戶有效交流的目的。因此,舉例說明的方式,對於共同認識和理解某個場景是很是有益的。

在選擇和描述每個例子時,做者提出要堅持「例子四原則」:

  • 例子老是明確的。

    • 不要使用"yes or no"這樣的問卷調查,應更注意彼此的溝通。
    • 不要使用「小於10」這樣的『比較性』描述,而使用「9」、「11」這樣明確具體的值,來對應不一樣條件下的場景。由於比較性的描述,老是意味着一個值域,而不是單個的值。相比引發變化的單個臨界值,這樣的值域對於咱們理解事例並無更多的幫助。
  • 例子老是完整的。

    • 要用具體的數值去表述不一樣條件下的不一樣場景,特別是要注意正反兩方面的數據輸入組合。對負數、0這樣的邊界值給予足夠重視。當涉及的條件是對象時,則要留意無效的引用、null等。
    • 使用替代的方法進行驗證。這個小節在原書的描述裏很是晦澀。我大意理解爲,對於一些新舊數據並存的系統,咱們很容易只惦記着新環境下的各類例子,而遺忘了舊數據也應該當被考慮在內。
  • 例子老是現實的。

    • 建議直接使用真實的數據,而不用費心爲測試專門編造數據。這樣能夠充分利用舊數據,減小將來可能的數據兼容風險,保證新舊數據在系統中的一致性。
    • 直接由用戶提供基礎的例子,而不要本身臆造。
  • 例子老是易於理解的。

    • 不要拿一堆的參數組合表格給客戶,咱們重點關注的應當是全部的臨界點。對每個臨界條件,都應當進行認真討論。若是雙方對該條件是否確係臨界條件有爭議,那說明雙方對例子的理解自己就是有誤解的。這彷佛又回到全部問題都要達成共識的這個原點上來了。
    • 注意尋找潛在的概念。在面對與一個功能聯繫的一大摞事例時,解決事例過於細碎、不易理解的方法,在於對事例適當進行抽象和概括,而後再轉頭分析此前那些瑣碎的下層概念,如同是切碎了再進行二次理解,這樣可能會發現一些潛在的概念。

在安全、性能等非功能性的需求方面, 當其重要性已經達到影響業務價值或者業務目標實現程度的時候,那就清晰地表達出來。這與《UML精粹》中的觀點是一致的,一切需求的重要性視其對實現業務價值、業務目標的影響程度而定。在性能、響應時間這些沒法準確表述的需求方面,做者引入了QUPER模型。對這個模型,我我的理解是預估這些指標對應的障礙,以及由此產生的開銷,而後再展開討論。每一個問題會被分紅三個方面:

  • 可用性:是否有功用性——「能不能用?」
  • 分化性:是否有市場佔有能力——「相比其餘產品是否是更具優點?」
  • 飽和性:過分設計沒有意義——「弄得再好一些有沒有實際意義?」

做者使用了「手機開機速度」做爲例子:不能開機,手機就沒用;開機速度太慢,就沒市場競爭力;開機很是快了,再快就沒有意義了。

提煉需求

好記性不如爛筆頭。交流的結果必定要以某種形式記載下來。原始的例子就象未經雕琢的鑽石,只有提煉後纔是關鍵的、易理解的、方便轉換爲可執行Specification的、能予以自動化測試的Key Example。

Specification應該是明確的、可測試的

這一點,和Example的」明確的」是一樣的含義。要儘量消除描述上的模棱兩可,而且要保證全部參與討論人員認識上的一致。

Specification應當是真實的互動,而不是簡單的腳本

腳本一般更側重於描述一個事物是如何變化的,更多的傾向於流程方面的內容。這也是客戶在描述的時候,容易掉入的一個陷阱——「先這樣,而後那樣,接着再怎麼樣,最後又是什麼樣」。這種腳本或者說是流程形式的表述,缺少一個系統的視角,缺乏對系統與用戶交互狀況的表達,並且流程自己很容易改變而且難以維護,因此並不適合直接做爲Specification。正確的方式,應該按」在這樣的狀況下,系統會作出那樣的反應「的形式進行表述。重點是」系統應該作什麼「,而不是」系統應如何工做」。

Specification應該是業務功能相關的,而不只僅是軟件設計意義上的結果

Specification不要與代碼、與UI等技術實現細節耦合太緊。技術層面的難題,以及流程等細節,留待再下層的自動化測試去解決。

Specification應該是自解釋的、不言自明的

爲了提升Specification的可閱讀性,能夠給它增添一段描述文本,而後交給其餘人看,靜靜地觀察對方的反應。若是對方無需額外提問就能理解並達成一致,那說明這個Specification符合預期。不然就把回答對方的解釋,也寫進開頭的這段描述性文本里。

在篩選關鍵事例時,應優先把握全部成功的場景,而把可能失敗的情景先放在一邊,由簡入繁地先把功能正常地展示出來。在具體篩選時,能夠從如下幾個方面着手:

  • 描述了業務功能的某個方面
  • 描述了重要的業務邊界或者業務規則
  • 描述了可能致使失敗的某種情形

Specification應該是專一的

使用Given-When-Then三段式表述Specification,而且儘可能避免考慮動做或者事件之間的依賴關係,最好就專一於一個動做、一個事件。對於新舊數據共存的系統,好比ES+CQRS裏新舊版本的領域事件,爲了保持專一,建議把這種兼容性問題壓入自動化測試層去解決。對於系統當中的缺省值,雖然能讓Specification更易讀,但考慮到這種缺省值若是表述在Specification中,將會致使過強的依賴性,因此也建議移入自動化測試層。在這個問題上,能夠參考「魔數」。當咱們把魔數顯式地定義出來時,才更有助於咱們消除誤解。將其移動到自動化測試層,或者放入一個全局的配置當中,都是相對更靈活的方法。

Specification應該是具有領域意義的

這一點又轉回到DDD了,即Specification中引用的概念、關係,都應該與當前上下文中的通用語言保持一致。

用自動化測試驗證需求

隨着軟件規模的逐漸增加,測試的數量、大小、應對變化的能力,都要求咱們採起自動化的測試方式。在這個過程當中,必須以預約的Specification再也不修改做爲前提,不然咱們的自動化測試只能是以訛傳訛了。換個角度看,雖然自動化測試增長了學習的成本,要引入額外的BDD工具,編寫額外的Feature與Specification,獲得的倒是先後一致的需求表達和更加輕鬆的後期維護,代碼的實現也會更天然。這一點,我認爲和DDD裏的從UL到代碼的展開是一致的。由於有統一的UL和清晰的模型,因此直接映射到代碼也就更直觀和天然了。

在具體實施自動化測試時,應該由簡入繁、從易到難,事先作好規劃。由於構建整個自動化測試的上下文環境是相對比較耗時費力的,這個上下文還要集成到必定的系統環境中才能執行,未來需求發生變化時這個環境也能被重用,因此很是有必要在對待整個測試環境規劃時更慎重一點。

在具體的實現環節,將自動化測試與編寫業務代碼同步,好比採用TDD的方法,能讓全部人把精力集中在測試上,保證Specification順利實現。這就如同公交車,若是中途沒有乘客上下,天然會跑得很快。但事實並不是如此,測試的職責就是保證Specification的實現、業務代碼的正確,因此自動化測試的規劃與實現,必需要由開發團隊承擔起來,不能交給其餘人。

手動測試與自動化測試的區別在於,手動測試側重於準備上下文,而後測試是否經過,關注的是成功與否;自動化測試則關心致使測試失敗的緣由。特別的,手動測試一般是腳本化的,一個步驟緊跟一個步驟,每一次測試都重複這個過程。若是測試失敗,那麼手動檢查其中的每一個步驟也在情理之中,反正都是手動的。自動化測試則必須消除這種測試步驟之間的依賴性,不然當測試失敗時,沒法肯定到底是哪一個步驟出了問題,自動化測試將所以退化成手動測試。若是遇到這種狀況,能夠將其切分爲若干個小的測試,好比每一個步驟對應一個小的自動化測試,改由測試上下文經過準備測試條件將這些小測試聯繫起來。由此引伸出一個問題:測試代碼要不要良好的組織與設計?答案是確定的。由於良好編碼的測試代碼,才能方便維護和閱讀,並做爲活文檔的提煉來源。

Executable Specification一般是用文本或者HTML格式進行描述的(想一想Cucumber的Step或者Spock裏的測試方法的描述式命名)。這樣當這個能夠執行的需求說明發生改變時,一般不須要從新編譯業務代碼。而自動化測試是代碼,並負責對Specification的驗證,因此當需求說明改變時,要從新編譯。此時,爲了不在Specification中混雜太多計算、判斷的邏輯,要把這些驗證邏輯放在自動化測試裏,而不要表述在Specification裏。由於對於Specification而言,在轉換爲Executable Specification時應當關注的是「測試什麼」,而把「如何測試」的責任交給自動化測試。

儘管測試不能是腳本,容易變化的流程應儘可能放在自動化測試層。可是不管怎樣,業務流程老是客觀存在的,經過When-Then的事件驅動方式,咱們能夠藉由若干個Specification的組合,展現完整的業務流程。而具體的業務邏輯,則放在Specification裏。因此,不要在測試代碼裏重複業務流程或業務邏輯。

UI的自動化測試

依賴UI與數據庫的測試,是自動化測試面臨的最大困難。書裏提到了許多建議,但都很是須要實踐進行驗證,才能深入領會。至少我只理解了皮毛,因此這一段的內容暫時沒有辦法總結。儘管如此,大量地引入Stub與Mock進行測試、隔離UI與業務模型、進行持久化無關的設計、創建統一的應用服務層、在Specification裏竭力避免引入UI與存儲相關的元素等等,都是可行的方案,相似MVC、MVP、MVVM等模式也將成爲咱們解耦的利器。

即便要對UI進行自動化測試,也建議使用針對UI編寫的Specification進行驗證,並且不要使用「錄製-回放」工具。由於這類工具生成的腳本一般會增長必定的學習成本,並且會很是難以理解,不便於維護。

從需求說明到UI的自動化測試,能夠從如下3個抽象力度逐漸弱化的不一樣層次進行實現。其中,需求說明應該在第一個業務規則層中描述,自動化層則應該經過組合第三個技術行爲層來表達第二個業務工做流層。這樣分層實現的從需求到測試的描述,更易於理解,也更高效。

  • 業務規則層 :測試要展現或者操做的是什麼? 對一條業務規則進行描述:對購買了5本書的客戶提供包郵服務。
  • 業務流程層 :如何經過UI使用某個功能,以更高的抽象級別進行描述? 對一個業務流程進行描述:放5本書進購物車,而後驗證是否提示已包郵。
  • 技術行爲層 :在一個流程的某個環節,須要哪些技術性步驟?對實現一個業務流程的具體UI操做步驟進行描述:點擊5本書的"放入購物車"按鈕,而後點擊「下單」,頁面顯示「已包郵」。

持久層的自動化測試

用數據庫做爲自動化測試的數據來源,是一個比較便利的方式,可是如何管理這些數據卻成爲一個難題。要避免直接使用舊存的數據做爲測試的數據源,由於這種數據與如今的需求可能存在衝突、不易理解。對於構造過程相對複雜的對象圖,能夠嘗試在數據庫裏預置相關數據,以提升自動化測試的速度。

在重構需求的同時,頻繁地進行同步驗證

這一部分的內容,與重構、持續集成緊密相關,所以提到的也可能是化整爲零、「不要想一口吃成一個胖子」、用Mock隔離故障點、以事件驅動測試、先保證同步再嘗試異步測試等等建議,而且提倡引入併發測試、快慢分組等方式儘快提升測試的反饋速度。

「業務時鐘」

對於那種週期性執行的測試,引入天然時鐘顯然是不合適的,因此新增「業務時鐘」的概念去控制這個週期,使之能夠根據測試要求隨時執行這一類的測試。個人理解,它等同於一個虛擬的時鐘,基本原理仍是觸發一個時間事件,而後利用這個事件去驅動測試。

提取活文檔

因爲提取活文檔更多的是BDD工具的使用,因此只要有編寫優雅的Specification和自動化測試做爲基礎,文檔的生成是一件水到渠成的事。在這個部分,主要的建議包括:

  • 以UI導航流程、功能結構、業務過程等組織文檔內容。
  • 即使是虛擬的角色,也要保證完整的角色信息。
  • 合理使用Tag、WiKi等一些文檔組織技術,提升可閱讀性。
  • 在文檔中始終保證領域專用語言DSL的統一。

寫在最後

實例化需求的實踐,重點和難點都在其中的4個環節:制定需求、描述需求、提煉需求和自動化測試。而《實例化需求》這本書自己的內容,也更偏重於用正反兩方面的具體事例來引導咱們的思考,涉及具體操做步驟的內容相對較少。因此和DDD同樣,掌握實例化需求的方法,也須要大量的實操和經驗積累。整理出這篇書摘,附上本身閱讀時的註解,但願能夠拋磚引玉、溫故而知新。

相關文章
相關標籤/搜索