在上一篇《Cucumber概念解析与Java入门实例 (上)》中,我们介绍了AAT,BDD,Cucumber的概念,并开始搭建了商品结算checkout实例,现在我们继续喽 : )
我们只需将之前Cucumber输出的最后一个代码片段复制并粘贴到一个新的Java文件中,现在可以先不着急理解具体的含义。让我们创建一个新的文件夹来存放我们的step定义:
$ mkdir step_definitions
现在在step_definitions中创建一个名为CheckoutSteps.java的Java文件。在文本编辑器中打开它,完成Jar包的导入和类定义的雏形,并粘入之前终端输出的代码片段:
package step_definitions;
import cucumber.api.java.en.*;
import cucumber.api.PendingException;
public class CheckoutSteps {
@Given("^the price of a \"(.*?)\" is (\\d+)c$")
public void thePriceOfAIsC(String arg1, int arg2) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@When("^I checkout (\\d+) \"(.*?)\"$")
public void iCheckout(int arg1, String arg2) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
@Then("^the total price should be (\\d+)c$")
public void theTotalPriceShouldBeC(int arg1) throws Throwable {
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
}
我们现在要做的是运行Cucumber,以便继续让它告诉我们下一步该做什么,但在这之前,别忘了我们需要编译新的CheckoutSteps类并将其添加到我们的类路径中。这是通常在使用IDE工作时不需要操心的事,但这次是命令行纯手工制造,含金量更高喽!
修改之前名为cucumber的shell文件,以便编译我们的Java代码的同时调用Cucumber:
javac -cp "jars/*" step_definitions/CheckoutSteps.java
java -cp "jars/*:." cucumber.api.cli.Main -p pretty --snippets camelcase \
-g step_definitions features
特别留意一点:我在以上书写的Cucumber的shell脚本,这是面向*nix用户的。Java类路径的语法取决于底层操作系统。 对于*nix操作系统,分隔符是冒号( : ),而对于Windows操作系统,分隔符是分号( ; )。
第1行编译我们刚刚创建的CheckoutSteps类,第2行调用Cucumber。
Cucumber调用有两个细节上的增添:
1. 将当前目录“.”添加到类路径中。
2. 添加-g step_definitions
命令行参数来告诉Cucumber在哪里找到step定义,而step定义正是负责将feature文件中的step与checkout应用程序(我们还没有实现)“粘连”在一起。
现在,让我们再次执行./cucumber,看看接下来需要做什么:
我们的scenario已经从”undefined”转为了”pending”状态。好消息喽!因为这意味着Cucumber现在正在运行第一个step,但是当它进行这里时,它执行到了我们复制和粘贴的step定义代码中throw new PendingException()的调用,其告诉了Cucumber这个scenario是仍在编写中的。我们需要用真正的实现来替换这个抛出的异常。
请注意,Cucumber报告其他两个step是被跳过的”skipped”状态。 一旦遇到failed或pending的step,Cucumber将停止运行scenario,并跳过剩余的step。
我们来实现第一个step定义。
我们已经考虑好checkout的第一个版本将是一个类,其包含一个把价格表和购买的项目作为传参的方法。所以,我们在对于Given the price of a banana is 40c的step定义的任务就是记住香蕉的价格。
在step_definitions文件夹中,编辑CheckoutSteps.java文件,使得第一个step定义如下所示:
@Given("^the price of a \"(.*?)\" is (\\d+)c$")
public void thePriceOfAIsC(String name, int price) throws Throwable {
int bananaPrice = price;
}
耶!我们的第一个step通过了!不过scenario仍然被标记为”pending”,因为我们还有两个step没有实现。
每当我们运行Cucumber时,都可以留意一下其输出中feature的整个内容。让我们切换到使用progress插件来获得更突出的输出吧。编辑脚本文件cucumber的输出部分,使整体变成这样:
javac -cp "jars/*" step_definitions/CheckoutSteps.java
java -cp "jars/*:." cucumber.api.cli.Main -p progress --snippets camelcase \
-g step_definitions features
相比于打印出整个feature,progress插件在输出中只打印出三个字符,每个step对应一个。第一个.
字符意味着通过的step。P
字符意味着第二个步骤正在pending。最后的-
字符意味着最后一步被skipped。
Cucumber有几种不同的插件,可以产生不同格式的输出,具体可使用指令
java -cp "jars/*" cucumber.api.cli.Main --help
查看。
编辑step_definitions/CheckoutSteps.java使得第二个step定义如这样:
@When("^I checkout (\\d+) \"(.*?)\"$")
public void iCheckout(int itemCount, String itemName) throws Throwable {
Checkout checkout = new Checkout();
checkout.add(itemCount, bananaPrice);
}
这段代码中我们试图在Checkout类的一个实例中调用add方法,以传递购买的项目及它们的价格。
这次运行./cucumber时,我们应该得到一个编译错误,因为我们还没有创建一个Checkout类:
我们的step没有通过,因为还没有定义一个可用的Checkout类。
我故意这样做是因为我们希望确保在着手项目开发之前,有一个功能全面的测试工具处于就绪状态。我们希望保持一种模式:我们可以相信测试工具的性能,因为我们已经看到它们提示了failed,这让我们可以确信,当测试显示pass时,我们确实完成的任务。而这其实正是outside-in development
的重要组成部分。
现在我们创建一个文件夹来保存我们的类实现:
$ mkdir implementation
并在该文件夹中创建一个称为Checkout.java的文件,并用文本编辑器修改如下:
package implementation;
public class Checkout {
public void add(int count, int price) {
}
}
我们需要在脚本文件cucumber中加入对该类的编译,同时修改classpath为项目的根目录,最终文件整体如下:
javac -cp "jars/*:." step_definitions/CheckoutSteps.java \
implementation/Checkout.java
java -cp "jars/*:." cucumber.api.cli.Main -p progress --snippets camelcase \
-g step_definitions features
同时不要忘记将Checkout类import入CheckoutSteps类中:
import implementation.Checkout;
并将bananaPrice变为实例变量,以使得其可以在iCheckout()方法中被使用:
public class CheckoutSteps {
int bananaPrice = 0;
@Given("^the price of a \"(.*?)\" is (\\d+)c$")
public void thePriceOfAIsC(String name, int price) throws Throwable {
bananaPrice = price;
}
第二个step也成功地通过喽。
我们将在下一篇《Cucumber概念解析与Java入门实例 (下)》中开始完成测试中重要的结果判断步骤。