Last Updated on 2019-09-09 by OneJar
Cucumber 是一個支援 BDD (Behaviour-Driven Development) 行為的自動化測試框架,支援多種常見的實作語言,包含 Java、Node.js、Go、Ruby 等。
關於 Cucumber 的入門介紹和基本術語可以參考以下文章:
本文示範如何在 Java 使用 Cucumber。
傳送門 (Table of Contents)
範例題目說明 —— TGIF
TGIF 指的是 Thank God It’s Friday!
TGIF 是 Cucumber 官方教學使用的範例題目,我覺得很有梗,所以本文教學沿用這個主題。但個人覺得官方範例為了簡化困難度,把範例邏輯改得不貼近實務,反而有點沒感覺,所以本文的範例內容會改良。
範例目標很簡單,就是實作一個功能,告訴我今天是不是星期五。
需求條列來說:
- 接受傳入一個日期
- 如果不是星期五,回傳「Nope」字串
- 如果是星期五,回傳「TGIF」字串
Prerequisites (前置工作)
環境準備:
- Java SE: 建議版本 8+
- Build tool: Maven 3.3.1+ 或 Gradle
- IDE: 任何你喜歡的編輯器,例如 IntelliJ IDEA 或 Eclipse
以下的示範是使用 Maven 和 IntelliJ IDEA。
1.建立一個全新 Cucumber 專案
Step 1-1: 使用 Maven plugin cucumber-archetype
建立專案
這裏使用 Cucumber 的 Maven archetype 幫忙建立專案:
$ mvn archetype:generate \
-DarchetypeGroupId=io.cucumber \
-DarchetypeArtifactId=cucumber-archetype \
-DarchetypeVersion=4.2.6.1 \
-DgroupId=com.onejar99 \
-DartifactId=HelloCucumberJava \
-Dpackage=com.onejar99.hellocucumberjava \
-Dversion=1.0.0-SNAPSHOT \
-DinteractiveMode=false
執行以上指令後會自動建立一個名為「HelloCucumberJava」的專案資料夾,並幫忙建立相關的資料夾結構和預設檔案:
$ tree HelloCucumberJava/
HelloCucumberJava/
├── pom.xml
└── src
└── test
├── java
│ └── com
│ └── onejar99
│ └── hellocucumberjava
│ ├── RunCucumberTest.java
│ └── Stepdefs.java
└── resources
└── com
└── onejar99
└── hellocucumberjava
10 directories, 3 files
如果檢視 pom.xml 檔,可以看到已經安裝幾個自動化測試需要的 package,包含 JUnit、cucumber-junit、cucumber-java:
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
Step 1-2: 驗證 Cucumber 安裝成功
在開始寫測試前,可以先快速驗證一下 Cucumber 是否安裝成功:
$ cd HelloCucumberJava/
$ mvn test
如果看到 BUILD SUCCESS
,就表示 Cucumber 安裝成功、運作正常:
Cucumber 執行完畢後,會告訴你執行了幾個測試步驟、幾個 Failed、有多少案例跳過等資訊。因為目前還沒寫任何測試,所以統計數字都是 0。
2. 撰寫測試案例
接著就可以打開編輯器開始寫測試案例的內容。
Step 2-1: 撰寫 Scenarios (.feature)
我們準備增加兩個測試案例:
- 一個是驗證 2020-01-01 不是星期五,回傳 Nope 字串
- 一個是驗證 2020-01-03 是星期五,回傳 TGIF 字串
新增一個 feature 檔:
$ vi src/test/resources/com/onejar99/hellocucumberjava/is_it_friday_yet.feature
Feature: Is it Friday yet?
Everybody wants to know when it's Friday
Scenario: 2020-01-01 isn't Friday
Given today is Year 2020, Month 1, Day 1
When I ask whether it's Friday yet
Then I should be told "Nope"
Scenario: 2020-01-03 is Friday
Given today is Year 2020, Month 1, Day 3
When I ask whether it's Friday yet
Then I should be told "TGIF"
如果你的 IDE 支援 Cucumber 語法,可能在編輯器上會看到一些方便的提示。例如下面是 IntelliJ IDEA 的介面,每個 Step 描述都有一層反灰,提醒 Step 還沒被定義:
這時候執行 mvn test
,會看到執行結果還是 BUILD SUCCESS
,但 Cucumber 會提示你有哪些 Step 因為還沒定義而被跳過沒執行:
甚至直接提供 Step 定義的程式碼範本:
Step 2-2: 撰寫 Step 定義 —— 貼上程式碼範本
我們試著把 Cucumber 提供的程式碼範本直接貼到專案裡:
$ vi src/test/java/com/onejar99/hellocucumberjava/Stepdefs.java
package com.onejar99.hellocucumberjava;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import static org.junit.Assert.*;
public class Stepdefs {
@Given("today is Year {int}, Month {int}, Day {int}")
public void today_is_Year_Month_Day(Integer int1, Integer int2, Integer int3) {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
}
@When("I ask whether it's Friday yet")
public void i_ask_whether_it_s_Friday_yet() {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
}
@Then("I should be told {string}")
public void i_should_be_told(String string) {
// Write code here that turns the phrase above into concrete actions
throw new cucumber.api.PendingException();
}
}
執行 mvn test
,結果訊息稍有不同。因為 Step 已經定義但拋出 PendingException
,Cucumber 會知道這個 Step 是「TODO」項目,在執行結果裡提醒你,並幫你跳過該 Scenarios 後續的 Step:
Step 2-3: 撰寫完整的 Step 定義
接著根據題目需求,完成真正的 Step 定義內容。直接基於程式碼範本繼續修改,修改重點:
- 定義每個 Step 的動作內容。
- 為了讓 Step 可以真的呼叫,我們會在產品程式新增一個
TGIFUtil
類別,定義好 API 介面,但還不需要實作 API 內容,這個階段專注在完成測試程式即可。 - 優化:重新命名 Step 參數名稱,提升可讀性,例如原本的
int1
,int2
,int3
,string
改成year
,month
,day
,answer
。 - 優化:將原本 Step Annotation 的參數化代號如
{string}
、{int}
優化成正規表示法的寫法。
$ vi src/test/java/com/onejar99/hellocucumberjava/Stepdefs.java
package com.onejar99.hellocucumberjava;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import java.time.LocalDate;
import static org.junit.Assert.*;
public class Stepdefs {
private LocalDate today;
private String answer;
@Given("^today is Year (\\d+), Month (\\d+), Day (\\d+)$")
public void today_is_Year_Month_Day(Integer year, Integer month, Integer day) {
this.today = LocalDate.of(year, month, day);
}
@When("^I ask whether it's Friday yet$")
public void i_ask_whether_it_s_Friday_yet() {
this.answer = TGIFUtil.isItFriday(this.today);
}
@Then("^I should be told \"([^\"]*)\"$")
public void i_should_be_told(String expectedAnswer) {
assertEquals(this.answer, expectedAnswer);
}
}
$ vi src/main/java/com/onejar99/hellocucumberjava/TGIFUtil.java
package com.onejar99.hellocucumberjava;
import java.time.LocalDate;
public class TGIFUtil {
public static String isItFriday(LocalDate date) {
// TODO
return null;
}
}
執行 mvn test
,會看到測試結果在驗證的 Step 「Then I should be told "OOO"」發生 Failed,這是因為我們還沒實作 TGIFUtil 的 API 內容,所以得到錯誤回傳,測試結果自然是 Failed。
但同時代表 Cucumber 自動化測試程式的部分已經就緒,只差真正的產品程式實作,這就是 TDD (Test-Driven Development) 裡的「紅燈測試」階段。
Step 2-4: 實作產品程式
到這裡只差一步,就是實作 TGIFUtil
裡的內容:
$ vi src/main/java/com/onejar99/hellocucumberjava/TGIFUtil.java
package com.onejar99.hellocucumberjava;
import java.time.DayOfWeek;
import java.time.LocalDate;
public class TGIFUtil {
public static String isItFriday(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return dayOfWeek == DayOfWeek.FRIDAY ? "TGIF" : "Nope";
}
}
執行 mvn test
,應該要看到全部測試都 Passed,也就是 TDD 中提到的「綠燈測試」 (除非你程式有 Bug,幫你發現 Bug 就是自動化測試的目的):
3. 優化:使用 Scenario Outline 參數化 Scenario
如果想多測幾個情境,依照上面的寫法,就是在 .feature
檔裡繼續增加 Scenario。
但每個 Scenario 的流程其實差不多,只是給予的日期、預期回傳的字串答案不同,如果每多測試一個日期就增加一個 Scenario 會讓原始碼很繁瑣累贅。
因應這種情況,Cucumber 提供了「Scenario Outline」的寫法,讓同樣的 Scenario 流程可以套用不同參數。
Scenario Outline 寫法要點:
- 原本的
Scenario:
改成Scenario Outline:
。 - 將要參數化的地方用
<OOO>
表示,例如<year>
,<month>
,<day>
,<answer>
。 - 增加一個 Examples 列表,將
<year>
,<month>
,<day>
,<answer>
設定多組想測試的值。
如此就能很輕易的為同一個 Scenario 新增不同的測試案例。
$ vi src/test/resources/com/onejar99/hellocucumberjava/is_it_friday_yet.feature
Feature: Is it Friday yet?
Everybody wants to know when it's Friday
Scenario Outline: Today is or is not Friday
Given today is Year <year>, Month <month>, Day <day>
When I ask whether it's Friday yet
Then I should be told "<answer>"
Examples:
| year | month | day | answer |
| 2020 | 1 | 1 | Nope |
| 2020 | 1 | 3 | TGIF |
| 2019 | 9 | 6 | TGIF |
| 2019 | 9 | 7 | Nope |
執行 mvn test
:
結語
本文示範如何用 Java 實作簡單的 Cucumber 範例,順便走了一次 TDD 的紅燈、綠燈流程。Cucumber 還有更多的使用細節和技巧,可以參考官方的教學文件。