上一节里我们定义了feature文件,feature文件就是自然语言描述的用例文件,它有一定的章法,具体的潜规则是:
Feature文件是测试人员与客户/产品经理进行需求交流的文档工具,定义好Feature文件以后,我们的测试功能点实际上已经是定义完成了,下面的步骤很自然就是用代码去实现这些测试点。 当然这里要说明的是,我们用人肉手点这些测试功能点也是没有太大问题的,但是想一想我们所处的开发阶段,现阶段我们只有整理成feature的原始需求,我们的系统还是一穷二白,一行代码都没有,因此用手点这个万能利器我们是无法使用的了。 再来思考一下如果我们使用代码去定义测试用例有什么好处?尽管系统目前没有任何功能,但这并不能妨碍我们把自动化的测试用例给写好(很大程度上,这是自动化的单元测试用例),有了用例,我们就有了标准。在后面的流程中只要开发人员编写的代码能够跑通这些自动化用例,那么我们可以比较有信心的认为开发实现了客户/产品经理所提出的需求。另外一点就是,自动化测试用例写完是可以重复使用的,在日后的回归测试、验收测试和持续集成的过程中,这些用例将起到保障产品/代码质量的关键作用。
回到命令行,我们在 cucumber_first/step_definitions
文件夹下新建1个名为 TodoStep.java
的文件
type nul > step_definitions/TodoStep.java
用 文本编辑器 编辑该文件。在教程的开始阶段我们并不需要使用Java IDE,因此你不需要纠结到底是使用eclipse还是intellij。在windows系统上,比较推荐的文本编辑器有 Notepad++, sublime, gvim
。当然人各有所其所好,选择任意一种都是可以的,不过因为sublime和gvim在设置文件编码上面没有Notepad++直观,因此对于初学者来说使用Notepad++是一种比较稳妥的解决方案。
先把上一节中cucumber提示我们的那一大串代码片段复制进 TodoStep.java
,然后加入包名和依赖包的导入,你的 TodoStep.java
文件应该是这样的:
package step_definitions; import cucumber.api.java.zh_cn.*; import cucumber.api.PendingException; import static org.junit.Assert.*; public class TodoStep { @假设("^我的任务清单里有(//d+)个任务$") public void 我的任务清单里有_个任务(int arg1) 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(); } @那么("^我还剩下(//d+)件未完成的任务$") public void 我还剩下_件未完成的任务(int arg1) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } }
看上去哪里有点奇怪。的确如此,我们的方法名是中文的,这不太符合一般的代码审美。
是时候重构一下方法名和参数名了。这次我们重构的驱动力有下面2个:
重构完成以后, TodoStep.java
中的代码应该是这样的:
package step_definitions; import cucumber.api.java.zh_cn.*; import cucumber.api.PendingException; import static org.junit.Assert.*; public class TodoStep { @假设("^我的任务清单里有(//d+)个任务$") public void iHaveSomeTasks(int totalTasks) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } @当("^我完成(//d+)件任务之后$") public void iFinishSomeTasks(int finishedTasks) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } @那么("^我还剩下(//d+)件未完成的任务$") public void iLeftSomeTasks(int leftTasks) throws Throwable { // Write code here that turns the phrase above into concrete actions throw new PendingException(); } }
重构完成之后我们发现我们的代码目的现在一目了然,增加了适当的可维护性。
注意:稳妥起见,请将 TodoStep.java
文件的编码改为gb2312。
运行我们刚定义的steps需要2个步骤
TodoStep.java
文件 看起来有点绕,其实在命令行中不难完成
首先执行编译命令:
# windows javac -cp "./jars/*;." step_definitions/TodoStep.java # linux or unix # javac -cp "./jars/*:." step_definitions/TodoStep.java
注意到我们把当前路径 .
加到了CLASSPATH里,这个很容易漏掉。
然后运行feature文件
java -cp "./jars/*;." cucumber.api.cli.Main -g step_definitions features
-g
选项告诉cucumber我们要执行的steps文件存放在哪个文件夹里。同样需要注意的是当前路径 .
必须放到CLASSPATH里。
如果没有错误的话,你会看到下面的信息
java -cp "./jars/*;." cucumber.api.cli.Main -g step_definitions features P-- 1 Scenarios (1 pending) 3 Steps (2 skipped, 1 pending) 0m0.134s cucumber.api.PendingException: TODO: implement me at step_definitions.TodoStep.iHaveSomeTasks(TodoStep.java:11) at ?.假设我的任务清单里有3个任务(todo.feature:6)
这个提示信息告诉我们,我们定义了1个Scenario和3个Steps,不过我们并没有实现这些。因为我们的实现方法里只是简单的抛出了1个Pending异常。
Pending异常很像是占位符,它告诉代码的维护者这个测试步骤应该实现,只是现在还没开工而已。
每次都在命令行里敲一长串命令除了会让你显得技术高超手法娴熟以外其实并不是什么特别有效率的事情(忽然想起来每次在命令行里用vim编辑文件各种前后台切换时总会有人发出惊叹)。而且命令越长出错的几率越大。
为什么不让这些命令自动执行呢?
先创建1个名为 compile.bat
的文件,我们用批处理的方式来自动运行命令。编辑之:
type nul > compile.bat # compile.bat javac -cp "./jars/*;." step_definitions/TodoStep.java
再创建1个名为 run.bat
的文件,输入如下内容
type nul > run.bat # run.bat java -cp "./jars/*;." cucumber.api.cli.Main -p pretty -g step_definitions features
现在我们可以把繁冗的命令简化成下面2个简单的指令了
compile run
如果你是在linux或unix机器上,你可以这样做
touch compile touch run echo 'javac -cp "./jars/*:." step_definitions/TodoStep.java' > compile echo 'java -cp "./jars/*:." cucumber.api.cli.Main -p pretty -g step_definitions features ' > run chmod +x compile chmod +x run ./compile ./run
目前为止我们知道了2件事情
下一节我们将实现我们的第1个cucumber step及断言