小黃瓜的驗收測試
Cucumber是一個以驗收測試為目標的框架,對碼農來說,就是有人(PM或參與的用戶)把使用者情境(或者說是使用者腳本)的過程寫出來,然後我們想法子匹配到我們的程式中,看看程試跑出的結果是否有符合腳本所要描述的狀態。
廢話少說,先體驗下:
- 首先開個目錄 cuke-first,然後在cuke-first/lib裡面放入cucumber-java.jar、cucumber-core.jar、cucumber-jvm-deps.jar、gherkin.jar
- 然後在cuke-first/features寫入我們的第一個腳本如下checkout.feature
# language: zh-TW
功能: 結帳
生鮮超市結帳測試腳本
場景: 香蕉結帳
假設"香蕉"每斤40元
當我買了1斤"香蕉"
那麼總價要付40元
|
- 看一下雛形寫法
cuke-first]$java -cp "lib/*:." cucumber.api.cli.Main -p pretty --snippets camelcase -d features
功能: 結帳
生鮮超市結帳測試腳本
場景: 香蕉結帳 # checkout.feature:4
假設"香蕉"每斤40元
當我買了1斤"香蕉"
那麼總價要付40元
1 Scenarios (1 undefined)
3 Steps (3 undefined)
0m0.000s
您可以用下面片段的程式來實作遺漏的步驟:
@假設("^\"([^\"]*)\"每斤(\\d+)元$")
public void 每斤元(String arg1, int arg2) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@當(("^我買了(\\d+)斤\"([^\"]*)\"$"))
public void 我買了斤(int arg1, String arg2) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@那麼("^總價要付(\\d+)元$")
public void 總價要付元(int arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
|
請注意:腳本中以雙引號(")夾起的文字會變成String參數,而所有的數字會變成int 參數
上述的參數說明:
上述的參數說明:
-p
|
表示輸出結果,若是值為 pretty,則會將整本腳本完整印出,若值為progress,則會用 . F - U P 分別表示 通過 失敗 跳過 未定義 暫停
其它可用的有 junit,html:報表目錄,json:輸出路徑與檔名 等用法
|
--snippets
|
雛形函式的名稱,值可用 underscore (以底線分隔), camelcase(第一字小寫,而後面每個字的第一個字大寫),因為我們使用中文腳本,函式名稱又自定義,所以這個參數沒有什麼用
|
-d
|
腳本所在的目錄
|
- 我不太喜歡Java用變數與方法用中文,所以先把英中對照表找出來
cuke-first]$java -cp "lib/*:." cucumber.api.cli.Main --i18n zh-TW
|
上面標(code)的表示是用在程式中的@Annotation,而沒有標(code)的表示是用在腳本的語法中
- 開始寫第一個測試程式到cuke-first/steps,並加以編譯
cuke-first]$ vi ./steps/CheckoutSteps.java
package steps;
import cucumber.api.java.en.*;
import java.util.*;
public class CheckoutSteps {
Map<String,Integer> pricesMap = new HashMap<>();
@Given("^\"([^\"]*)\"每斤(\\d+)元$")
public void thePerKiloPriceOfFruitI(String fruit, int price) throws Throwable {
pricesMap.put(fruit,price);
}
@When(("^我買了(\\d+)斤\"([^\"]*)\"$"))
public void iCheckout(int kilo, String fruit) throws Throwable {
//TODO:把水果加入結帳
summary += kilo * pricesMap.get(fruit);
}
@Then("^總價要付(\\d+)元$")
public void theTotalPriceShouldBeDollars(int total) throws Throwable {
//TODO:測試總價是否正確
assert summary == total : "結帳價格計算錯誤";
}
}
:x
cuke-first]$ javac -cp ".:lib/*" ./steps/*.java
|
- 進行測試
cuke-first]$ java -cp "lib/*:." -ea cucumber.api.cli.Main -p pretty -g steps features
# language: zh-TW
功能: 結帳
生鮮超市結帳測試腳本
場景: 香蕉結帳 # checkout.feature:4
假設"香蕉"每斤40元 # CheckoutSteps.thePerKiloPriceOfFruitIs(String,int)
當我買了1斤"香蕉" # CheckoutSteps.iCheckout(int,String)
那麼總價要付40元 # CheckoutSteps.theTotalPriceShouldBeC(int)
1 Scenarios (1 passed)
3 Steps (3 passed)
0m0.075s
|
上述多了一個 -g 參數,目的在於將程式與腳本結合,因為測試程式放在 steps目錄下,而腳本放在 features下,所以必須用 -g 告訴 cucumber,將程式與腳本串聯(glue)在一起
腳本寫法說明
- 首先第一行使用 # language: zh-TW 說明我們的腳本是用正體中文書寫,每個階層習慣以兩個空白字元作縮排。
- 用 功能: 開頭,接著用短文字作為這腳本的名稱,例如上述的 結帳;若是有詳細說明,請換行寫(可容許多行),例如上述的 生鮮超市結帳測試腳本
- 然後以 場景:、劇本:、場景大綱: 或 劇本大綱: 開頭,開始描述要測試的內容,內容至少要包含一句 假設、假如 或 假定 開頭 的句子,可用 # 來表示註解,範本如下:
# language: zh-TW
功能: 結帳
劇本大綱: 買香蕉結帳
假設”香蕉”每斤40元
# <count>與 <total>變數會對映到下面的例子的 count、total 參數,
當我買了<count>斤"香蕉"
那麼總價要付<total>元
#例子中的兩個參數會代入上面的 <count> 與 <tottal> ,也就是會用兩組參數各執行一次測試
例子:
| count | total |
| 1 | 40 |
| 2 | 80 |
場景: 兩串蕉分別掃條碼
假設”香蕉”每斤40元
當我買了1斤"香蕉"
並且結帳時又加了1斤"香蕉"
那麼總價要付80元
場景: 買1斤香蕉和一斤蘋果
假設”香蕉”每斤40元
同時"蘋果"每斤25元
當我買了1斤"香蕉"
並且結帳時又加了1斤"蘋果"
那麼總價要付65元
|
- 照前述的第3點,重新產生一下雛形寫法,會發現腳本寫了那麼長,卻只產生幾個函試,所以可以確定一點,cucumber的測試是以讀取腳本,然後以 Regex 找尋符合的函式,一一執行,所以要小心語法相同,卻作不同事的描述。
- 腳本寫作的原則,一次只測一件事,最好只縮減到只剩 假設 當 那麼,就像作實驗一樣,過多的可變因子會導致錯誤時,卻不知道是因為什麼因素而失敗。
結合框架
回頭看上面的步驟,會發現,所有的工作的進入點都是一支 cucumber.api.cli.Main 程式,然後透過參數告訴cucumber.api.cli.Main應該怎麼作,而通常我們作測試,並不會用這麼粗爆的方式,最常見的就是結合測試框架,例如 JUnit 與 TestNG 。
JUnit的進入點(測試程式,在此要用到cucumber-junit.jar 與 cucumber-html.jar)
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty", "html:target/cukeReport"}, snippets = SnippetType.CAMELCASE,
features = {"src/test/resources/features"},glue = {"kentyeh.steps"})
public class RunCukesTest {
}
|
說明:
- Cucumber.class如同之前的cucumber.api.cli.Main一樣,是Cucumber Junit測試程式的進入點,@CucumberOptions而就相當於之前的參數設定,所以一個專案內就只應存在這一組,用多組進入程式或是多個@CucumberOptions可能會導致對可知的後果
- features指示Cucumber ,腳本的存放路徑,這樣子Cucumber才知道要從那裡讀取腳本
- glue指示腳本測試程式的package名稱,例如之前的 CheckoutSteps.java就應該放在這個package下,那麼cucumber才會在讀取腳本的時候,才知道到那個package下去找Matched的程式進行驗收測試。
TestNG的進入點(要用到cucumber-testng.jar)
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import cucumber.api.testng.AbstractTestNGCucumberTests;
@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty", "html:target/cukeReport"}, snippets = SnippetType.CAMELCASE,
features = {"src/test/resources/features"},glue = {"kentyeh.steps"})
public class RunCukesTest extends AbstractTestNGCucumberTests{
}
|
說明:跟Junit沒太多的差別,唯一不同的就是繼承AbstractTestNGCucumberTests
Spring 的 Autowired(要用到cucumber-spring.jar)
若您是 spring + maven 的開發者,可以用以下指令產生範例專案
$mvn archetype:generate -DarchetypeGroupId=com.github.kentyeh \
-DarchetypeArtifactId=springJdbiArch -DarchetypeVersion=2.2
經問答式產出專案後進入專案目錄執行以下指令進行測試
$mvn test
或是可以以下指令,提示cucumber腳本step程式雛形
$mvn -Pcuke initialize
其它部分請參考專案首頁
|
Guice(要用到cucumber-guice.jar)
沒有用過,不過應該與Spring差不多
從這個索引目錄可以看出,有其它可以配合的框架,請自行從官方的GitHub學習。
留言
張貼留言