在需求的開發過程中,最令人困惑的地方就在於需求模糊。需求是解決業務的問題,那麼驗收的方式應該是由業務方提出,但是往往業務方(可能是產品經理,也可能是直接是客戶)只能給出比較模糊的一個驗收標準,而程式卻是需要非常明確的輸入輸出的條件的。
這中間的鴻溝是否能夠通過一些手段來減輕(個人認為是無法完全消除的,資訊在傳遞的過程中一定會經歷一些損耗),Cucumber 就是一個為此提出的例項化需求
框架。從這個框架提供的思路在於讓業務方提供明確的場景,讓開發為場景提供資料進行模擬,通過Cucumber進行銜接。
Cucumber 本身的定位是一個 BDD(Behavior Driven Development,行為驅動開發) 的測試框架。BDD是一種敏捷軟體開發的技術,它鼓勵軟體專案中的開發者、QA和非技術人員或商業參與者之間的協作。BDD側重設計,要求大家在設計測試用例的時候對系統進行定義,使用通用的語言將系統的行為描述出來,將系統設計和測試用例結合起來,從而以此為驅動進行開發工作。
假設我需要投票系統,在做出一系列分析和設計之後,得到這樣的需求:
##### 目標
為方便收集大家的反饋和活動構建一個`投票系統`
##### 投票功能設計
【使用場景】
1. 每一人`每輪`只能投出指定的有限的`票數`
2. 可以根據配置展示`每輪`的`投票結果`
- 總是展示`投票結果`
- `每輪`投票完成之後才展示`投票結果`
【管理場景】
... ...
【編輯場景】
... ...
在設計這個功能的時候,我們已經需要開始注意需求中使用的詞彙了,否則大家交流的功能的時候,可能就會發生歧義。在定義完這些需求之後,我們需要將這些需求沉澱到專案中。
Cucumber 使用了一個Gherkin
的標記語言,有點像是YAML
,的,Cucumber
直譯器可以通過理解使用Gherkin
編寫的*.feature
檔案來執行對應的測試檔案。採用Gherkin
有兩個目的:
- 例項化需求,澄清驗收標準
- 自動化測試。
通過Gherkin
形成業務方能閱讀理解的檔案形式。這裡我們挑中投票使用場景中的一個功能編寫一個例子:
@投票功能
Feature: 【投票-使用場景】投票功能
每一人`每輪`只能投出指定的有限的`票數`
Scenario Outline: 每位`參與人``每輪`只能投出指定的有限的`票數`
Given 發起一輪投票,設定`選項`
| A | B | C |
Given <recipientNumber>個`參與人`,每人投票<voteTimes>次
When 所有`參與人`完成投票
Then `投票結果`中總票數應該為<totalVotes>票
Examples: 正常情況的用例
| recipientNumber | voteTimes | totalVotes |
| 2 | 1 | 2 |
| 3 | 2 | 6 |
Examples: 異常情況下的用例
| recipientNumber | voteTimes | totalVotes |
| 4 | 2 | 4 |
這裡將Feature中定義出這個功能。接著在下面寫出這個功能對應的場景Scenario
。然後通過Given
、When
、Then
等關鍵字定義這個場景中的步驟。然後在下方通過Example
關鍵字將用例中關鍵的資訊用表格的形式列出。
通過以上這些關鍵字加上對場景的描述,最終將需求落到的專案中,假如希望語言閱讀更加連貫,這些關鍵字也有對應的中文關鍵字支援編寫。
Gherkin
的語法在網站上有比較詳細的描述,關於它的語法可以參考:https://cucumber.io/docs/gherkin/reference/
編寫這樣的一個feature
檔案之後可以將它放在test
目錄的resources
資料夾下:
.
├── java
│ └── io.github.whthomas.demo.cucumber
└── resources
├── cucumber-reporting.properties
├── cucumber.properties
└── io.github.whthomas.demo.cucumber.vote
├── displayVoteResultScenario.feature
└── useVoteScenario.feature
在沒有 Java 程式碼的情況之下,編寫一個RunCucumberTest
類:
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty"})
public class RunCucumberTest {
}
執行RunCucumberTest
,會給出如下提示,告知我們要怎麼做:
建立和 feature 檔案同級的test
目錄的src
目錄,將這些提示程式碼拷貝到這個同package的類中(新建 Java 類,名稱可以根據需求和場景來取名),Java 類中就是一些驅動測試場景的程式碼了。Cucumber會根據Feature檔案中的描述的步驟,逐一執行Java中的程式碼,並將引數傳遞進去。
這個時候目錄結構應該這些構建:
.
├── java
│ └── io.github.whthomas.demo.cucumber
│ ├── RunCucumberTest.java
│ └── vote
│ ├── DisplayVoteResultScenario.java
│ └── UseVoteScenario.java
└── resources
├── cucumber-reporting.properties
├── cucumber.properties
└── io.github.whthomas.demo.cucumber
└── vote
├── displayVoteResultScenario.feature
└── useVoteScenario.feature
每次去執行RunCucumberTest
的時候,就會在 IDE 中像展示文件一樣,將需求和用例結合展示出來。
Cucumber
也提供了一些第三方外掛,將結果通過報告展示出來:
但是我覺得展示的效果,還是太偏向於是給技術人員閱讀的了。如果我們力求一份好的文件能讓業務人員也能很好理解,可能還是需要利用它產生的json檔案自己來開發一下才行。
現在在回過頭來看BDD的這個套路,把這整個套路整理一遍,這一切看起來還挺美好的:
- 需求分析和定義,整理需求
- 設計產品的功能,制定驗收標準。
- 編寫
Gherkin
檔案,將需求例項化到文件中,同時編寫業務用例。 - 構建測試程式碼,測試業務程式碼。
- 長期持續整合/持續測試
但是實際操作中,會遇到的問題還是挺多的:
- 設計產品功能的時候,就能定義好
通用語言
- 驗收標準具備可測量性
- 業務用例考慮能否充分。從何而來,是否有足夠的場景支撐
- 業務程式碼本身是否具備可測試性
- 基礎設施是否能長期支撐 CI/CD
- 團隊是否能接受這種開發的模式
... ...
當然我們不能指望一個工具可以解決需求模糊的問題,Cucumber
只是去解決這個問題中的一環,團隊更加需要的是一套流程、一系列實踐和改變的決心。它需要團隊成員的通力合作,才可以幫助整個團隊更好的理解業務,理解軟體,理解這個複雜的客觀世界。
文章對應的程式碼已經放在了GitHub上:https://github.com/whthomas/cucumber-demo
附:如何安裝Cucumber
如果要使用Cucumber
在專案中可以新增如下的依賴:
<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>
如果是使用JUnit5
,可以把第二個依賴包改成是:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>