l

2023年3月27日 星期一

使用ezSpec落實行為驅動開發與實例化需求(10):撰寫自訂報表

March 25 12:44~14:18

▲圖1:ezSpec實作Visitor設計模式類別圖

 

前言

上一集介紹ezSpec的基本報表功能,這一集介紹如何擴充ezSpec,撰寫使用者自訂報表。

***

Visitor設計模式

為了支援自訂報表,ezSpec讓Gherkin keyword實作Visitor設計模式,請參考圖1。在GoF設計模式中,有兩個角色:

  • Element:圖1中的SpecificationElement介面,要接受「拜訪者」的物件需要實做Element介面。該介面有一個accept方法,接受visitor作為參數。該方法的實作會呼叫(callback)visitor.visit,讓visitor可以讀取element的資料。
  • Visitor:圖1中的SpecificationElementVisitor介面,想要拜訪各個Element的具體拜訪者(concrete visitor)需要實作該介面,將拜訪者的邏輯寫在visit方法裡面。在ezSpec中,客製化報表就是一個具體拜訪者。

 

圖2為Feature實作SpecificationElement介面的程式碼,第14行Feature先將自己傳給visitor,接著再將它身上每一個Story傳給visitor。由於Feature是整個Gherkin keyword中最外層的結構,只要拜訪Feature,就能夠拜訪整個Feature file裡面所有的元素,包含Scenario、Scenario、Background與Step。



▲圖2:ezSpec的Feature實作SpecificationElement的程式碼

 

***

 

實作SpecificationElementVisitor產生報表

ezSpec內建的文字檔報表,就是使用上述介紹的機制所產生,請參考圖3。第20行到60行的visit方法,就是用來「拜訪」Feature內的所有元素,產生報表的邏輯。

  

▲圖3:ezSpec內建產生文字報表的Visitor程式碼

 

***

客製化報表範例

開發好ezSpec之後,Teddy就拿它來描述ezKanban的規格。圖3為ezKanban的MoveLane使用案例的規格,用ezSpec的Scenario Outline撰寫。這個功能是將看板系統中,工作流程內部的某一個Lane複製到另一個地方。在描述規格的時候,直接以表格描述複製之前與複製之後Workflow的內容,其執行結果所產生的文字報表如圖4。


▲圖4:以ezSpec所撰寫的ezKanban系統之MoveLane使用案例規格 

 

圖5的內容不太容易閱讀,因為規格中表格還包含著另一個表格,因此Teddy想:「既然MoveLane使用案例是看板視覺化業務邏輯的其中一個功能,為什麼不用視覺化的方式來表達這個功能?」要怎麼用是視覺化方式產生報表,幫它寫一個Visitor吧,請參考圖5。


▲圖5:執行圖3所產生的ezSpec內建文字報表 

 

Teddy使用Markdown的Mermaid擴充(外掛)來繪製Workflow,圖6的WorkflowMermaidVisitor與ezSpec內建的PlainTextVisitor類似,差別在於前者針對MoveLane規格產生Markdown報表,後者則是標準的文字報表。


▲圖6:撰寫WorkflowMermaidVisitor產生MoveLane使用案例的專屬報表 

 

圖7報表的內容與圖4相同,前者以圖形顯示,後者以文字顯示。在MoveLane使用案例的情境中,以圖形顯示比較容易閱讀。


▲圖7:執行圖3所產生的客製化Markdown報表 

 

只要將寫好的Visitor在驗收測試的afterAll()方法中去拜訪Feature,再將結果寫入檔案中,就可以在每次執行驗收測試之後產生新的客製化報表,請參考圖8。


▲圖8:針對MoveLane使用案例驗收測試產生客製化報表

***

結論

寫到這裡剛好第十集,也把ezSpec目前的功能介紹完畢。Teddy預計在2023年七月之後開兩門新課:

  1. Clean TDD(整潔測試驅動開發)
  2. Living Documentation in Agile Development(敏捷開發中的活文件)

在課程中(特別是第二門課)會以ezSpec為範例,在開課前Teddy會先開源ezSpec讓有興趣的鄉民們使用,敬請期待。

***

友藏內心獨白:寫完文件要回頭寫Code。

2023年3月26日 星期日

使用ezSpec落實行為驅動開發與實例化需求(9):內建報表

March 25 10:43~11:48


▲等待報表功能全部完成,ezSpec就可以開源了

 

前言

前幾集介紹ezSpec的基本功能以及同步執行功能,今天介紹ezSpec最後一個功能:報表。報表功能是做到Living Documentation(活文件)的核心功能之一,先用ezSpec描述系統行為,然後這些可執行規格變成驗收條件,執行結果透過報表整理,產出與系統現狀同步的Living Documentation。

目前ezSpec支援產生個別Feature file執行結果的報表,報表格式為txt檔與json檔,整合性報表還在開發中。ezSpec支援Visitor設計模式,開發人員也可以自行撰寫Visitor,產生使用者自訂報表。本集先介紹如何產生內建報表,下一集介紹如何擴充ezSpec,撰寫使用者自訂的報表。

***

產生報表

要讓ezSpec產生報表,首先測試案例(Test Class)要實作EzSpec介面,請參考圖1。

 

▲圖1:測試案例實作EzSpec介面

 

EzSpec介面程式碼如圖2,它身上有四個Annotation:

  • @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class):這是JUnit 5的內建annotation,在JUnit報表中會將test mehtod的名字的底線取代為空白,以方便閱讀。例如,將scenario_using_EzScenario_annotation顯示為scenario using EzScenario annotation。
  • @ExtendWith(EzSpecReportExtension.class):@ExtendWith是JUnit 5支援使用者自行擴充功能的annotation,ezSpec的EzSpecReportExtension透過實作JUnit 5 的AfterAllCallback介面,讓JUnit執行每一個Test Clase之後呼叫ezSpec,以便產生報表。
  • @EzSpecReportFormat({EzSpecReportFormat.Format.json, EzSpecReportFormat.Format.txt}):這是ezSpec自訂的annotation,用來表示產生那些格式的內建報表。目前ezSpec支援txt與json這兩種報表格式
  • @Tag(EzSpecTag.LivingDoc.EzSpec):這也是JUnit 5內建的annotation,用來分類測試案例。參考圖3,ezSpec內建EzSpecTag介面,裡面包含常用的Tag。將Test Class(在ezSpec的情境中,也就是Feature file)用Tag分類,主要以下兩個目的:
    • 在test suite中作為測案案例的選擇條件,例如只挑選單元測試,或是只挑選整合測試。
    • 作為Living Documentation的分類標籤,例如產生Use Case的報表,或是產生在Staging環境測試執行結果的報表。


▲圖2:EzSpec介面程式碼

 

▲圖3:EzSpecTag介面程式碼片段

 

只要測試案例(Test Class)實作EzSpec介面,並依據前幾集介紹的方式撰寫Feature, Story, Scenario, Scenario Outline,執行測試案例之後,ezSpec就會在預設的目錄 target/ezSpec-report 產生報表,請參考圖4。

 

▲圖4:ezSpec在預設目錄中產生報表

 

圖5與圖6分別展示ezSpec產生的純文字檔報表與json格式報表。


▲圖5:ezSpec純文字檔報表範例

 

▲圖6:ezSpec JSON格式報表範例

 

***

不要產生報表

有時候你可能會臨時不要產生某個測試案例的報表,此時可以直接在測試案例上面加上@DisableEzSpecReport。

 

▲圖7:加上@DisableEzSpecReport就不會產生該測試案例的報表

 

如果想要禁止ezSpec產生所有測試案例的報表,可以設定EZREPORT環境變數,把它設為OFF即可。什麼時候會取消報表產生功能?因為產生報表需要時間,也需要寫入檔案,因此會讓測試案例跑得比較慢一點。原本這也不是什麼大問題,但如果你的專案採用類似PiTest這種Mutation Test工具,那麼同一個測試案例會被執行N次,而且在這種情況下,開發人員只需要觀看PiTest產出的報表,並不需要ezSpec的報表。此時,關閉ezSpec產生報表功能,可以加速PiTest執行。

 

***

結論

報表系統算是ezSpec的最後一哩路,目前ezSpec提供的報表屬於個別驗收測試(Feature file)執行結果的報表,雖然使用者可以透過下一集介紹的方式擴充ezSpec產生使用者自訂報表,但Teddy還是想在ezSpec中提供基本的整合報表功能。等待整合報表功能完成,Teddy就會將ezSpec開源給有興趣的鄉民們使用。

***

友藏內心獨白:人要衣裝,佛要金裝,報表也是很重要的功能。

2023年3月25日 星期六

使用ezSpec落實行為驅動開發與實例化需求(8):規範平行行為

March 24 21:45~23:14

▲圖1:電梯的規格範例

 

前言

ezSpec與Gherkin相關的基本功能已經介紹完畢,今天介紹一個Gherkin沒有的功能:「描述平行行為的規格」。

Gherkin的Given, When, Then, And, But這些Step依據它們出現在Scenario的先後順序依序執行,對於一些天生就具有平行處理能力的系統,例如在IoT(Internet of Things)系統中,多個sensor或device彼此之間都是獨立且平行執行。在這種情況下,用Gherkin就無法表達這些平行執行的行為。

三月初的時候實驗室一組研究IoT的學生跟Teddy介紹他們用python開發的工具—concurrentSpec,它擴充Gherkin語意讓開發人員撰寫並執行同步行為規格。原本Teddy開發ezSpec並沒有計畫要支援描述平行處理行為,聽完學生的介紹,覺得加上這個功能可以讓描述行為的語意變得更完整,因此花了點時間在ezSpec中支援這個功能。

***

平行運作行為的範例

請參考圖1,該例子節錄自 <Specifying Internet of Things Behaviors in Behavior-Driven Development: Concurrency Enhancement and Tool Support> 這篇論文,論文作者是實驗室IoT小組的成員以及Teddy的指導教授鄭老師。論文中提到一個描述電梯規格的例子,這個例子參考自 Jackson所寫的兩本書:《Software Requirements & Specifications: A Lexicon of Practice, Principles and Prejudices》與《Problem Frames: Analysing and Structuring Software Development Problems》,圖1的中文敘述是請ChatGPT幫忙翻譯。

這個例子如果用標準的Gherkin執行,假設第29行執行失敗(無法打開緊急指示燈),測試案例就會停在第29,後面的assertion就不會被驗證。在電梯的例子中,這種行為很顯然是不正確的。因為就算29行與30行都失敗,只要第28行成功(電梯有停在最近的樓層),第39行就應該被檢查(電梯門要在五秒內打開)。

有些測試框架可以讓使用者設定:「就算某一個assertion失敗,測試案例還是持續執行」。但就算是採用這種方式來執行圖1的例子,最後結果還是可能錯誤。例如第28行失敗但第31行成功,這表示:「電梯沒有成功停在最近的樓層,但是電梯門最後卻打開了。」這顯然不是使用者所期待的行為。

怎麼用Gherkin描述同步行為?concurrentSpec提出一個很簡單的擴充方式:「在不增加Gherkin keyword的前提之下,將Given, When, Then當作同步執行群組的起頭,它們之後的And與But將會與它們同步執行。整個同步群族執行完畢之後,才會開始執行下一個同步群組。」換句話說,Given, When, Then彼此之間是循序執行,但它們之後若接著And或But,這些And/But將與它們平行執行。

此外,還可以指定每一個Step如果執行失敗,之後的Step是否要繼續執行。

***

用ezSpec描述圖1行為的程式如圖2所示,Teddy故意讓「打開緊急指示燈」與「取消該叫車請求」發生錯誤(839行與842行),但是這兩個Step如果發生錯誤,下一個Given/When/Then依然會繼續執行(因為指定ContinuousAfterFailure參數)。請注意,圖2中的Step執行順序如下,Given, When, Then, Then是循序執行,第一個Then與後面兩個And為平行執行。

  • Given
  • When
  • Then, And, And (834、838、841行):三個Step平行執行
  • Then

圖2中有一個小細節要注意,就是要讓Scenario以平行的方式執行,必須呼要ExecuteConcurrently()


▲圖2:用ezSpec描述電梯規格範例

 

圖3顯示圖2執行結果,可以看到兩個And執行失敗並沒有影響後續Then的執行。


▲圖3:圖2執行結果報表

***

電梯門打不開

Teddy修改一下電梯的Scenario,故意讓電梯沒有停在最近樓層,請參考圖4。





▲圖4:模擬電梯沒有停在最近的樓層。

 

修改後的Scenario執行結果如圖5,可以看到因為Then沒有加上ContinuousAfterFailure參數,所以整個同步執行群組執行完畢之後,就不會繼續執行下一個Step。


▲圖5:圖4執行結果報表

***

看到這裡鄉民們可能會想:「不管電梯有沒有停在最近樓層,我就是要執行最後一個步驟的檢查啊!」也可以,修改一下Then,幫它加上ContinuousAfterFailure,請參考圖6。

 

▲圖6:加上ContinuousAfterFailure參數,即使電梯未停妥也繼續執行後續驗證步驟

 

修改後的Scenario執行結果如圖7,如果系統行為真的如此,恭喜你,找到一個bug。什麼bug?電梯未停靠最近樓層,但電梯門卻打開了。


▲圖7:圖6執行結果報表

***

 

結論

如果鄉民們的系統與IoT系統類似,有著很明顯的平行行為,那麼標準的Gherkin語意就無法描述這些行為。借用concurrentSpec所擴充的Gherkin語意,可以在不增加Gherkin keyword的前提之下,使用ezSpec描述平行系統的行為。

ezSpec指定行為的功能差不多介紹完畢,下一集介紹ezSpec產生報表的功能。

***

友藏內心獨白:快到山頂了。

2023年3月24日 星期五

使用ezSpec落實行為驅動開發與實例化需求(7):將共用步驟寫在Background

March 23 21:08~22:20


▲第七集了,快到山頂了沒XD

 

前言

前幾集介紹如何撰寫Scenario與Scenario Outline,基本上已經可以處理絕大部分的問題。今天介紹Background,讓鄉民們可以共用不同Scenario之間相同的步驟。

***

Background也是一種Scenario

ezSpec將Background也視為一種Scenario,但它身上通常只有Given與And,用來準備不同Scenario中共用狀態的內容。請參考圖1,首先使用第42行newBackground()開啟一個新的Background。與Scenario和Scenario Outline相同,Background一定要隸屬於某一個Story。

新增完Background之後,就可以和寫Scenario一樣,撰寫Step。圖1第44行將backgroundSideEffect這個data member的值設為100,然後在47行將UserId設為UUID。


▲圖1:ezSpec的Background範例

***

 

圖2的Scenario因為可以直接使用到Background的Given與And,所以它就不需要再撰寫一次Given與And,所以它的第一個Step直接就是第61行的When。在第63行中檢查backgroundSideEffect的值使否為100,如果是,就表示Background有先輩執行,接著再執行這個Scenario。第66行則是讀取Background所新增的UserId。


▲圖2:在ezSpec的Scenario中讀取Background所設定的資料範例

 

圖3為執行圖2所產生的報表,第9到13為Background的內容,第15~18行為圖2的內容。由這個報表可以很清楚的看出來,雖然Background可以達到重用Step的優點,但使用它的Scenario閱讀起來會變得有點……斷頭…..的感覺。Given不見了,直接從When開始。也就是說,使用者要從重用性與可讀性之間做出取捨。當然也可以修改報表產生程式,重複將Background的Step複製到每一個使用它的Scenario,以提升一點可讀性。


▲圖3:執行使用Background的Scenario所產生的報表

***

 

結論

寫到這裡,ezSpec的基本功能算是介紹完畢。在Gherkin 6中新增一個Rule關鍵字,用來將Scenario分類到某一個業務規則。Teddy覺得這個關鍵字可以用JUnit 5內建的@Tag annotation達到類似效果,所以暫時沒有在ezSpec中直接支援。

ezSpec除了基本的Gherkin語法以外,也參考了concurrentSpec,支援描述併行行為的功能,下一集介紹這個功能。

***

友藏內心獨白:共用經常會降低可讀性。

2023年3月23日 星期四

使用ezSpec落實行為驅動開發與實例化需求(6):Scenario Outline

March 21 18:06~19:30

▲一個Test Method,N個綠燈

 

前言

上一集介紹在ezSpec中四種撰寫Scenario Outline的方式(請參考<使用ezSpec落實行為驅動開發與實例化需求(5):撰寫Scenario Outline的四種方法>),所使用的Examples是比較簡單的單一表格。這一集介紹當Scenario Outline的Examples比較複雜的時候,該如何表達這些Examples。

***

JUnit 5的ArgumentsProvider

在說明如何表達複雜的Examples之前,先介紹兩個基本的介面/類別:ArgumentsProvider與Junit5Examples。

JUnit 5 的@ParameterizedTest原本就支援好幾種提供測試資料的方式,上一集使用最簡單的@CsvSource,這一集要用ArgumentsProvider來提供測試資料。參考圖1,ArgumentsProvider是一個介面,只有一個provideArguments method,回傳Stream<? extends Arguments>。

Arguments也是一個介面,參考圖2,它身上有一個get mehtod,回傳 Object 陣列,這就是存放參數的地方。

 

▲圖1:JUnit 5的ArgumentsProvider 介面

 

▲圖2:JUnit 5的Arguments介面

***

ezSpec的Junit5Examples

ArgumentsProvider是JUnit 5 設計給的@ParameterizedTest使用的介面,ezSpec的Scenario Outline接受的參數是Examples,兩者介面不同。因此Teddy設計一個Junit5Examples抽象類別,透過它可以轉換ezSpec的Examples以及 JUnit 5的ArgumentsProvider,如圖3所示。

 

▲圖3:ezSpec的Junit5Examples抽象類別

***

 

RenameWorkflow Use Case的Scenario Outline

參考圖4,假設你要幫ezKanban的RenameWorkflow使用案例定規格,你想到兩個主要的Scenarios:

  • Scenario 1:新的名稱與舊的名稱不同,執行RenameWorkflow使用案例之後會產生一個領域事件。
  • Scenario 2:新的名稱與舊的名稱相同,執行RenameWorkflow使用案例之後不會產生領域事件。

針對這兩個Scenario,你一共找出五種不同的測試資料,例如把Workflow的名字從dev改成deV、從dev改成DEV,或是從dev改成dev。

 

▲圖4:Rename a workflow 範例

 

圖4的Scenario Outline,要如何用ezSpec來表達?首先定義測試資料,請參考圖5與圖6。

 

▲圖5:不同Workflow名稱的測試資料(Examples)

 


▲圖6:相同Workflow名稱的測試資料(Examples)

 

準備好Examples之後,就可以在Scenario Outline中使用它們。參考圖7,採用上一集所介紹的方法一來撰寫Scenario Outline,唯一不同處在第61行:

WithExamples( Junit5Examples.get(different_name_examples.class),

                          Junit5Examples.get(same_name_examples.class))

直接指定Examples的class name來獲得測試資料。

 

▲圖7:使用Junit5Examples的具體類別當作測試資料來源提供給ezSpec

 

另外,different_name_examples與same_name_examples也可以作為@ParameterizedTest的資料來源,用來執行Scenario Outline的,請參考圖8。


▲圖8:使用Junit5Examples的具體類別當作測試資料來源提供給@ParameterizedTest

***

更複雜的測試資料:表格中還有表格

請參考圖9,這是提供給ezKanban的MoveLane使用案例的驗收測試資料,其中givenWorkflow表示在Given階段初始化的Workflow狀態,expectedSubStageAsRootStage表示移動Lane之後Workflow的狀態。

 

▲圖9:Examples包含表格的範例

 

MoveLane的Scenario Outline如圖10所示,可以看到在89行指定了<given_workflow>參數(圖9中的givenWorkflow表格),以及在123行指定了<expected_workflow>參數(圖9中的expectedSubStageAsRootStage表格)。


▲圖10:MoveLane Scenario Outline範例

 

圖10的執行結果如圖11所示,可以看到第22是最外層的Examples表格,其內部的given_workflow與expected_workflow這兩個column各是另一個表格。可以透過ezSpec很簡單的設計複雜的驗收測試資料,使用上也很簡單。


▲圖11:MoveLane Scenario Outline執行結果報表(只顯示部分)

***

結語

透過ezSpec的Junit5Examples可以設計同時讓ezSpec與JUnit 5的ParameterizedTest所使用的驗收測試資料,每一種測試資料獨立定義在一個Junit5Examples類別中,容易管理與撰寫。

寫到這裡,常用的ezSpec功能已經介紹完畢。下一集介紹Background,可以用來將相同的Given寫在一起,以利重複使用與減少重複程式碼。

***

友藏內心獨白:Table內的Table,還是Table。

2023年3月22日 星期三

使用ezSpec落實行為驅動開發與實例化需求(5):撰寫Scenario Outline的四種方法

March 21 09:47~12:28

▲圖1:Scenario Outline範例

 

前言

前兩集介紹在ezSpec中撰寫Scenario的方式(請參考<使用ezSpec落實行為驅動開發與實例化需求(3):撰寫Scenario與傳遞簡單參數> 與 <使用ezSpec落實行為驅動開發與實例化需求(4):在Scenario中使用表格>)。當你開始採用BDD(行為驅動開發)或SBE(實例化需求)開發系統,針對同一個Feature或Story中的多個Scenario,很可能發生執行步驟相同或相似,只有輸入的資料以及在When中所用來檢查的expected result不同的情況。此時可考慮將這些Scenario改寫成Scenario Outline,用以簡化feature file的撰寫。

ezSpec支援四種不同的方式撰寫與執行Scenario Outline,以下逐一介紹。

***

方法一:用For Each執行Scenario Outline

Scenario翻譯成劇情場景,Scenario Outline則是劇情大綱場景大綱。簡單來說,Scenario Outline就是餵一組資料給Scenario,讓這個Scenario執行好幾次。用傳統測試的術語,這種測試稱為參數化測試案例資料驅動測試案例

圖1是一個簡單的Scenario Outline,其目的為依據商品未稅金額計算含稅總價,一共有四個例子(四筆測試資料),分別涵蓋四捨五入、可開立零元發票以及兩個邊界條件。為了撰寫這四個例子,最無腦的方式是把它們寫成四個Scenario。但是這樣一來這四個Scenario除了測試資料不同以外,其他的執行步驟都一樣,也就是說寫成四個Scenario會產生很多重複程式碼,此時改用Scenario Outline就可以解決這個問題。

為了儘量與JUnit 5整合,ezSpec提供好幾種撰寫Scenario Outline的方式。首先看到圖2:

  • 第74行:執行 newScenarioOutline() 在Story身上產生一個新的Scenario Outline。
  • 第75行:用 WithExamples(examples) 將指定該Scenario Outline所使用的Examples(也就是測試資料)。
  • 第76行:呼叫 RuntimeScenarios() 獲得List<RunimeScenario>。Scenario Outline會依據所輸入的Examples資料筆數,產生N個RunimeScenario。
  • 第77行:使用 forEach(example -> {} ) 的方式來指定每一次執行RunimeScenario的Step。

之後的寫法就跟一般的Scenario一樣,唯一的差別在於讀取Examples參數的方式。在標準的Scenario中,透過env.row() 可以讀取表格中的資料。在Scenario Outline的情況下,RunimeScenario每次只會看到一筆Examples的資料,此時要用env.getInput().get(column_name) 的方式來讀取Examples的資料,如圖2的82~85行所示。

 

▲圖2:ezSpec的Scenario Outline範例,採用forEach執行

 

由於圖2的Scenario Outline只是一般標準的test method,因此在JUnit的報表中,只會看到一筆執行結果,如圖3。

 

▲圖3:圖2執行結果的JUnit報表

 

雖然JUnit報表中只會有一筆資料,但ezSpec產生的報表會包含Scenario Outline每次的執行結果,請參考圖4。


▲圖4:圖2執行結果的ezSpect報表

***

方法二:用@ParameterizedTest執行Scenario Outline

如果開發人員希望在JUnit的報表中看到Scenario Outline每一次執行的結果,可以改用JUnit 5的@ParameterizedTest來執行Scenario Outline,請參考圖5。JUnit 5的ParameterizedTest直接將每次執行的測試資料透過test method的arguments接收資料,ParameterizedTest支援多種指定測試資料(相當於圖1的Examples)的方法,圖5所示為透過@CsvSource的方式指定測試資料。

使用ezSpec透過ParameterizedTest撰寫Scenario Outline的方式與圖2類似,除了將原本的@Test換成@ParameterizedTest,以及使用@ParameterizedTest所規定的設定測試資料方式以外,主要有以下兩點不同之處:

  • 不需要自行使用for each:因為JUnit 5的ParameterizedTest會自動執行該test method好幾次,因此開發人員就不需要如同圖2中透過RuntimeScenarios().forEach的方式來執行RuntimeScenarios。
  • 用WithParameterizedExamples取代圖2的WithExamples:由於ParameterizedTest的測試資料不包含表格的表頭,因此需要透過WithParameterizedExamples將測試資料的表頭告知ezSpec,如此一來在描述每一個Step的時候,若使用到 <tax_excluded> 等變數時,ezSpec才知道要如何將這些變數替換成測試資料中實際的數值。

 

▲圖5:使用@ParameterizedTest執行ezSpec的Scenario Outline

 

使用ParameterizedTest有一個好處,就是JUnit的報表可以顯示每次Scenario Outline的執行結果,如圖6所示。


▲圖6:圖5執行結果的JUnit報表

 

***

方法三:用@EzScenarioOutline執行Scenario Outline

開發人員也可以使用ezSpec的@EzScenarioOutline來執行Scenario Outline,如圖7所示。在這種情況下就不需要使用RuntimeScenarios().forEach的方式來執行RuntimeScenarios,ScenarioOutline的內容除了要呼叫WithExamples以外,基本上與Scenario沒什麼差別。主要差異有以下三點:

  • @EzScenarioOutline要自行執行執行次數,以圖7的例子,因為測試資料有四筆,因此指定@EzScenarioOutline(4)
  • test method需要接受一個型別為RepetitionInfo的參數,如圖7第119行。
  • 要改用Execute(repetitionInfo.getCurrentRepetition())來執行RuntimeScenarios,如圖7第138行。

 

▲圖7:使用@ParameterizedTest執行ezSpec的Scenario Outline

 

 

使用@EzScenarioOutline可以達到和@ParameterizedTest類似的效果,在JUnit的報表中顯示每次Scenario Outline的執行結果(但無法顯示文字說明),如圖8所示。


▲圖8:圖7執行結果的JUnit報表

***

方法四:用@DynamicScenario執行Scenario Outline

最後一種執行Scenario Outline的方式,使是用ezSpec的@DynamicScenario,請參考圖9。@DynamicScenario的撰寫方式和@EzScenarioOutline大致相同,不同點有:

  • test method須回傳DynamicNode,如圖9第141行。
  • DynamicExecute()取代Execute(),如圖9第160行。
  • return整個DynamicExecute執行結果,如圖9第149行。

 

▲圖9:使用@DynamicScenario執行ezSpec的Scenario Outline


使用@DynamicScenario在JUnit的報表中可以顯示最詳細的資料,除了每次Scenario Outline的執行結果,還包含每一個Step的內容與執行結果,如圖10所示。

 

▲圖10:圖9執行結果的JUnit報表

***

結語

本集介紹四種不同撰寫與執行Scenario Outline的方法,除了撰寫方式略有不同,以及JUnit產生的報表有所差異以外,其執行結果ezSpec所產生的報表基本上都是相同的。開發人員可以選擇自己認為比較喜歡的與法表達方式來撰寫Scenario Outline。

在本集中Scenario Outline的Examples是一個簡單的表格,下一集將介紹如何在Scenario Outline中指定表格中含有表格的測試資料。

***

友藏內心獨白:太多選擇也是一種煩惱。

2023年3月21日 星期二

使用ezSpec落實行為驅動開發與實例化需求(4):在Scenario中使用表格

March 21 05:40~6:45

▲圖1:使用ezSpec在Scenario中讀取單一表格資料範例,表格內容取自《BDD in Action》一書

 

前言

上一集介紹如何使用ezSpec撰寫Scenario、從Step描述文字內容中讀取參數、以及在不同的Step Definition之間傳遞資料的方法(請參考<使用ezSpec落實行為驅動開發與實例化需求(3):撰寫Scenario與傳遞簡單參數>),今天介紹如何在Scenario中讀取表格資料。

***

讀取單一表格

請參考圖1的85~87行,ezSpec支援Gherkin的表格,以 | 符號分隔不同的column。通常表格的第一列為表頭,之後每一列為表格內容。ezSpec支援兩種讀取表格的方式:

  • row(int).get(column_name):第一種讀取表格方式請參考圖1的88~90行,env.row(0)拿到表格中第一個row,也就是 |    Jill    |   100,000     |   800           |   這一組資料。接著可以用get(column_name)拿到這個row中不同column的資料,例如 env.row(0).get("owner")拿到Jill。
  • row(first_column_data_in_each_row).get(column_name):圖1的93~95行展示第二種讀取表格方式,用每一個row第一個column的資料當作key,使用env.row(first_column_data_in_each_row)的方式定位到所要讀取的該筆row,然後再用get(column_name)拿到這個row中不同column的資料。例如env.row("Jow").get("points")拿到50,000。

使用表格可以一次指定多筆測試資料,提高Scenario的可讀性。

***

讀取多個表格

你也可以在一個Scenario中使用多個表格,例如把一個表格當作Given裡面的輸入資料,另一個表格放在Then裡面當作expected result,請參考圖2。

讀取多個表格資料的方式與讀取單一表格相同,但有一點需要注意:「在ezSpec中,一個Scenario同一時間最多只會有一個Active Table。」在圖1中由於該Scenario只定義了一個表格,因此一但表格定義之後,在接下來的Step Definition(Given-When-Then-And)中都可以讀到該表格的資料。在圖2中,第一個表格定義在Given,第二個表格定義在Then。因此Given以及When會讀到第一個表格的資料,而Then則是讀到第二個表格的資料,請參考圖2第171~172行(後面定義的表格會蓋掉上一個定義的表格)。


▲圖2:使用ezSpec在Scenario中讀取多個表格資料範例,表格內容取自《BDD in Action》一書

***

結語

這兩集介紹在ezSpec中撰寫Scenario的方式,有使用過Cucumber、SpecFlow或JBehave的鄉民,應該可以很明顯發現兩者的不同之處。因為ezSpec是一種Internal DSL,使用「類Gherkin語法」以Java程式語言採用Specification by Example的方式,用例子(Scenario)來表達規格。

ezSpec的Step Definition以Lambda實作,和其相對應的Step直接綁定,不需要因為Step的文字內容改變而重新產生一個新的Step Definition,可以讓開發人員專心在撰寫Scenario上面,少掉很多工具所帶來的干擾。

採用BDD或Specification by Example的開發方式,經常會產生「執行步驟相同但驗證資料不同的Scenario」,此時就可以用下一集介紹Scenario Outline來簡化這些相似的Scenario。

***

友藏內心獨白:一表值千言。