cucumber java從入門到精通(3)簡單實現及斷言
上一節里我們定義了step的java代碼實現文件,step就是測試步驟及斷言的集合,我們先定義出來,以后可以驅動開發以及在持續集成時重用。
這一節我們將近距離細觀一下所謂的step java實現。以下面的代碼片段為例:
public class TodoStep { //1
@假設("^我的任務清單里有(\\d+)個任務$") //2
public void iHaveSomeTasks(int totalTasks) throws Throwable { //3
// Write code here that turns the phrase above into concrete actions
throw new PendingException(); //4
}
}
- //1 定義了public class,這沒什么好說的;
- //2 假設注解,這個注解表明下面的方法對應的也就是feature文件中
我的任務清單里有xxxx個任務
這個步驟; - //3 定義了具體實現feature文件步驟的方法,並從feature定義中取得傳入參數,也就是xxxx個任務的具體值;
- //4 拋出Pending異常,表明該步驟暫未實現,但來日方長,也許有天可以實現;
cucmber執行順序
如果你對上面的代碼尚有疑問,那么是時候看一下cucumber的執行順序了,以下面代碼片段為例:
# feature
假設 我的任務清單里有3個任務 // 1
當 我完成1件任務之后 // 2
那么 我還剩下2件未完成的任務 //3
# step
@假設("^我的任務清單里有(\\d+)個任務$") //4
public void iHaveSomeTasks(int totalTasks) throws Throwable { //5
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@當("^我完成(\\d+)件任務之后$") //6
public void iFinishSomeTasks(int finishedTasks) throws Throwable { //7
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@那么("^我還剩下(\\d+)件未完成的任務$") //8
public void iLeftSomeTasks(int leftTasks) throws Throwable { //9
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
- 當我們運行了
run
命令后(還記得上一節的run嗎?其實就是執行了cucumber.api.cli.Main。),cucumber會去找feature文件,然后執行第1條feature語句,也就是//1 - 執行//1的時候,cucumber會去找對應的step定義文件,尋尋覓覓的過程中,cucumber發現了對應的注解,也就是//4
- //4告訴cucumber,下面緊接着定義的那個方法就是該feature對應的代碼實現,於是cucumber再去執行//5
- 在執行//5的時候,cucumber將3作為totalTasks這個參數傳給//5
- cucumber執行//5方法體中的內容並收到pending異常,於是該feature執行結束,轉去執行下1條feature
小練習:你能自己說明//2以及//3的執行順序嗎
漸進重構之假裝實現
回憶一下我們的測試目標,我們要測試一個todo list,第1步也就是在測試背景或者叫做前置條件里,我們是這樣描述的
假設 我的任務清單里有3個任務 // 1
這時候不妨假設我們有個TodoList類,該類有個getTotalTaskCount()方法返回任務清單中一共有多少條任務。基於這個想法,我們重構一下TodoStep.java文件
@假設("^我的任務清單里有(\\d+)個任務$")
public void iHaveSomeTasks(int totalTasks) throws Throwable {
// totalTasks == 3
TodoList todo = new TodoList();
assertEquals(todo.getTotalTaskCount(), totalTasks);
}
在這里要說明的是注解中的(\d)表示獲取feature定義中的數字字符並把該數字(int)作為totalTasks參數傳入iHaveSomeTasks方法。因此totalTaks應該等於3。另外assertEquals是jUnit的斷言方法
執行一下compile && run
,得到下面的結果
step_definitions\TodoStep.java:11: 錯誤: 找不到符號
TodoList todo = new TodoList();
^
符號: 類 TodoList
位置: 類 TodoStep
step_definitions\TodoStep.java:11: 錯誤: 找不到符號
TodoList todo = new TodoList();
^
符號: 類 TodoList
位置: 類 TodoStep
2 個錯誤
我們無法編譯,因為TodoList這個類並沒有定義。
下面我們定義一下TodoList類。在implementation文件夾中新建1個名為TodoList.java的文件,該文件的實現是
// TodoList.java
package implementation;
public class TodoList {
public int getTotalTaskCount() {
return 3;
}
}
下面在TodoStep.java中導入TodoList類
// TodoStep.java
import implementation.TodoList;
修改一下compile文件,因為這次我們需要TodoList.java文件,修改完成的版本應該是這樣的
# compile
javac -cp "./jars/*;." step_definitions\TodoStep.java implementation\TodoList.java
# linux && unix
javac -cp "./jars/*:." step_definitions\TodoStep.java implementation\TodoList.java
然后運行compile && run
,得到下面的結果
#language: zh-CN
功能: 任務管理
場景: 完成1件任務 # todo.feature:5
假設我的任務清單里有3個任務 # TodoStep.iHaveSomeTasks(int)
當我完成1件任務之后 # TodoStep.iFinishSomeTasks(int)
cucumber.api.PendingException: TODO: implement me
at step_definitions.TodoStep.iFinishSomeTasks(TodoStep.java:19)
at ?.當我完成1件任務之后(todo.feature:7)
那么我還剩下2件未完成的任務 # TodoStep.iLeftSomeTasks(int)
1 Scenarios (1 pending)
3 Steps (1 skipped, 1 pending, 1 passed)
0m0.128s
cucumber.api.PendingException: TODO: implement me
at step_definitions.TodoStep.iFinishSomeTasks(TodoStep.java:19)
at ?.當我完成1件任務之后(todo.feature:7)
我們看到有1個step pass了,那就是我們剛定義前置條件step,所以到現在為止我們干的不錯!我們有了feature文件,該文件描述了需求和測試用例,我們完成了測試step中的前置條件,並且我們實現了1個真正的TodoList類,盡管這個類目前還是嗷嗷待哺,不過當我們實現更多的step之后,TodoList類的功能會進一步完善,直到滿足用戶需求。實際上我們現在做的就是BDD,用測試去驅動開發。
可能有的同學會對此不屑一顧,TodoList類到目前為止只是自欺欺人的實現了1個返回int型數字3的方法,離我們所要的任務清單還相差十萬八千里。其實不用擔心這個,我們的TodoList類沒有實現具體的功能是因為我們實現的測試步驟還不夠多,我們接收到的需求還只是那么一點點,當我們實現更多步驟之后,TodoList自然會羽翼豐滿。這就是所謂的最小實現原則。
漸進重構,重構剩下的2個步驟
下面我們重構剩下的2個步驟。
我們先假設TodoList有1個finishTask方法,每次調用這個方法就會完成n個任務,n從參數傳入。於是剩余的任務就會減去n。
我們再假設TodoList有1個getRestTasksCount方法,調用這個方法可以獲取剩下的task的數量。
重構完成以后,我們的代碼如下所示
package step_definitions;
import cucumber.api.java.zh_cn.*;
import cucumber.api.PendingException;
import static org.junit.Assert.*;
import implementation.TodoList;
public class TodoStep {
TodoList todo;
@假設("^我的任務清單里有(\\d+)個任務$")
public void iHaveSomeTasks(int totalTasks) throws Throwable {
// Write code here that turns the phrase above into concrete actions
todo = new TodoList();
assertEquals(todo.getTotalTaskCount(), totalTasks);
}
@當("^我完成(\\d+)件任務之后$")
public void iFinishSomeTasks(int finishedTasks) throws Throwable {
// Write code here that turns the phrase above into concrete actions
todo.finisheTask(finishedTasks);
}
@那么("^我還剩下(\\d+)件未完成的任務$")
public void iLeftSomeTasks(int leftTasks) throws Throwable {
// Write code here that turns the phrase above into concrete actions
assertEquals(todo.getRestTasksCount(), leftTasks);
}
}
編譯一下,不出意外的失敗了,沒關系,我們再去重構TodoList.java文件。
我們先實現finishedTask方法,這個方法的方法體是空,表示什么都不做;
然后實現getRestTasksCount方法,這個方法簡單的返回2就好;
#TodoList.java
package implementation;
public class TodoList {
public int getTotalTaskCount() {
return 3;
}
public void finishTask(int count) {
}
public int getRestTasksCount() {
return 2;
}
}
再次運行compile && run
可以看到,我們的step全部運行通過了
#language: zh-CN
功能: 任務管理
場景: 完成1件任務 # todo.feature:5
假設我的任務清單里有3個任務 # TodoStep.iHaveSomeTasks(int)
當我完成1件任務之后 # TodoStep.iFinishSomeTasks(int)
那么我還剩下2件未完成的任務 # TodoStep.iLeftSomeTasks(int)
1 Scenarios (1 passed)
3 Steps (3 passed)
0m0.132s
總結
從這一節可以看到,我們的cucumber BDD行為驅動開發的流程是這樣的:
- Describe behaviour in plain text
- Write a step definition in Java
- Run and watch it fail
- Write code to make the step pass
- Run again and see the step pass
- Repeat 2-5 until green like a cuke
小練習:翻譯上面的6句話,弄清楚具體的意思。對照教程,請指出每一步與教程中所對應的代碼
我們簡單實現了TodoList類,目前的TodoList類是滿足我們的feature的需求的。下一節我們將增加更多的測試數據,以便完善TodoList類。