March 14 18:10~19:02;20:28~23:23
▲圖1:ezSpec領域模型
前言
行為驅動開發(Behavior-Driven Development;BDD)與實例化需求(Specification by Example)是兩種主流的測試驅動開發方法。藉由開發人員與領域專家或利害關係人一起探討需求(協同建模),以及代表需求的具體範例,並將其表達在自動化測試案例中,以達到活文件(Living Documentation)的效果。
Teddy在開發ezKanban的過程中,在2021年順手作了ezSpec—它是用Java撰寫的Internal DSL,可以簡化使用Cucumber或JBehave等工具的麻煩(請參考<無痛將驗收測試文件寫在測試案例中>)。Teddy當初開發ezSpec是想要做Living Documentation的研究,後來ezKanban的事情太忙,做好ezSpec之後只有在ezKanban中使用它,一直沒有時間進一步把它做到完整的支援Living Documentation。這陣子因故又把ezSpec拿出來「積極開發」,等開發到一段落準備把它開源。在開源之前先寫幾篇文章來介紹ezSpec,日後也可當作ezSpec的使用文件。
***
ezSpec的領域模型與使用範例
圖1是ezSpec的領域模型,基本上ezSpec參考Gherkin語法所設計,差別在於ezSpec可以描述Story以及同步執行步驟。這些細節之後再詳細說明,先介紹領域模型各個物件的意義:
- Feature:功能,代表可以提供使用者價值的單位。關於Feature的「粒度大小」有不同的用法,有人將「整個功能模組」當成一個Feature,然後再透過Story或Scenario切成比較小的功能。例如,把「結帳」當成一個Feature,裡面包含現金結帳、信用卡結帳、xxx Pay等。也有人把Feature當成一個單獨功能使用,粒度大小相當於傳統的使用案例(Use Case)。
- Story:故事,就是敏捷社群經常使用的User Story。Gherkin/Cucumber本身並沒有支援Story,但JBehave有。有些人主張Story只是開發過程中用來「切割需求」的一種任務分派的單位,最後交付給使用者的是Feature。既然Gherkin是用來描述需求的一種語法,不需要使用Story去紀錄開發過程的「臨時性產品」。但許多傳統敏捷社群的人可能習慣用Story來做為需求溝通的最小單位,所以認為還是有Story比較好。ezSpec支援Story,一個Feature可以有一到多個Story。
- Scenario:劇情,基本上一個Scenario可以想像成Specification by Example裡面的Example,透過舉例來溝通需求。一個Feature或Story具體來說到底要做什麼?用舉例的方式來表達,比較具體明白,也可以減少誤會。幫一個Feature或Story列舉出幾個具代表性的Scenario之後,團隊如果覺得已經可以瞭解這個功能的內容,這些例子就成為這個功能的驗收條件。如果可以將這些例子(也就是Scenario)變成程式自動執行,它們就成為自動化驗收測試。圖2為ezSpec的Scenario範例,由於ezSpec是一個Internal DSL(Domain Specific Language),使用ezSpec描述規格時就跟寫程式是類似的,直接把Gherkin的Given-Then-Then內容寫在lambda裡面。
▲圖2:ezSpec Scenario範例,pending()表示該Step尚未實作
圖3為ezSpec執行圖2的Scenario所產生的報表。
▲圖3:ezSpec產生的Scenario執行結果報表
***
- Scenario Outline:劇情大綱,幫一個Feature或Story舉了幾個例子之後,如果發現這些例子的執行步驟都相同,只有輸入的資料與執行結果不同,就可以將這些個別的Scenario整理成一個Scenario Outline。Scenario Outline基本上就是傳統軟體測試中的「參數化測試」或是「資料驅動測試」,在Gherkin中,提供不同資料的方式是指定一組Examples。請參考圖4的ezSpec ScenarioOutline範例,範例表格資料內容參考自https://reurl.cc/qkrL9y。
▲圖4:ezSpec ScenarioOutline範例
圖4第160~177行是指定Examples的地方,兩組Examples一共以五筆測試資料。第179~187行新增一個Scenario Outline,整個Scenario Outline寫在JUnit 5 的test method內,執行結果與一個一般的測試案例相同,可以在JUnit報表中看到,如圖5。
▲圖5:Scenario Outline執行結果
ezSpec可以將整個Feature的執行結果產生文字報表,圖6為上述Scenario Outline執行結果所產生的報表,可以看到每一輪執行的輸入資料,以及每一個步驟(Step,請參考後面說明)的執行結果。
▲圖6:ezSpec產生的Scenario Outline執行結果文字報表
***
- Runtime Scenario:Scenario在ezSpec是一個抽象類別,在執行期間,每一個Scenario,以及Scenario Outline展開之後每次執行都會產生一個Runtime Scenario物件,用來記錄輸入資料與執行結果。
- Background:背景,請參考圖7,類似JUnit的@BeforeEach,可以將多個Scenario的共同步驟定義在Background中,然後再重複使用。Background雖然達到減少重複描述步驟的好處,但是會讓Scenario變得比較不容易閱讀,使用時要稍作取捨。
▲圖7:ezSpec Background範例
- Step:步驟,一個Scenario可以包含多個Step。Given、When、Then、And、But這些都屬於Step的一種類型。
- Given:用來描述執行功能的前置條件。
- When:用來描述執行功能,也就是傳統測試所說的「執行待測程式」。
- Then:用來驗證結果功能執行後的結果。
- And, But:在Given、When、Then之後可以加上And與But作為接續步驟。
- Concurrent Group:Gherkin的Step只支援循序執行,也就是你無法用Gherkin描述同步行為的規格。Teddy的指導教授鄭老師指導一組研究IoT的學生,用Python開發了一個稱為concurrentSpec的工具,可以使用原本Gherkin的語法來描述IoT系統地同步行為規格。原本ezSpec也只支援循序的Step,最近一個月才參考concurrentSpec,加上描述同步行為的能力。
***
結語
傳統使用Cucumber、SpecFlow或是JBehave,都是先用Gherkin撰寫純文字的feature file(或是 story file),再透過工具產生Step Definition Method/Function,然後開發人員負責實作這些Step Definition Method,把規格變成可執行文件。
這種方式,原本是希望領域專家或利害關係人可以直接用Gherkin採用舉例的方式描述需求,再交由開發人員將其轉成自動化驗收測試。概念很好,但這種方式有兩個很大的問題,首先領域專家或利害關係人大該都不會用Gherkin直接幫你寫規格,他們願意口頭跟你溝通、討論,而且是持續地溝通、討論,就已經很了不起了。其次,Step Definition Method很難寫,也不好閱讀與維護。因為要用到很多正規表示式將feature file所描述的內容傳給程式,要先學會不同工具對於Step Definition的繁瑣撰寫規範,實際使用時很容易出錯,讓開發人員無法專心在描述規格與範例上面。既然到頭來feature file與Step Definition都要開發人員自己寫,為什麼不用「開發人員友善」的方法與工具,來做BDD/SBE呢?
改用Internal DSL的方式,直接拿掉煩人的Step Definition與正規表示式,整個BDD/SBE/TDD流程變得很順暢。用Internal DSL的方式並不是Teddy發明的,有人用Ruby做過,只是Teddy一直到2021年在《Living Documentation: Continuous Knowledge Sharing by Design》書中看到一個類似的例子之後,才引發自己也用Java開發類似工具的念頭。
Teddy自己使用的結果表明,真的是方便很多。這一集先到這邊,之後再詳細逐一介紹ezSpec的每一項功能。
***
友藏內心獨白:軟體還沒Open,文件先Open。
沒有留言:
張貼留言