为什么写这篇 Helper

《The Cucumber For Java Book》这本书对于刚入门的新手来说过于不友好, 一个是 Java 世界的人不习惯用命令行来操作,大多数人是用 IDE 来写 Java, 另一个是多数时候 Cucumber 多数时候是结合 Sprint boot 等框架一起编写,很少这样Core Java直接编写,书上的编写习惯给人感觉好像来自Ruby 世界,但是 Ruby 世界的命令千锤百炼,哪里像Java命令行那么丑, 对于有些想学习 BDD 的管理型教练来说还是有点晦涩,故此有了这篇 Helper 文章。

The Helper of Cucumber for Java Book

The Cucumber For Java Book - Behavior-Driven Development for Testers and Developers
Written by Sea Rose, Matt Wynne, and Aslak Hellesoy
Source Code:https://pragprog.com/titles/srjcuc/the-cucumber-for-java-book/

Environment and Command Line

这本书是需要安装 JDK 并设置对应的环境变量的,否则没法在 terminal 里面直接运行 Java 命令,可自行 Google JDK installation, 大概要做到两个部分:

  • 安装LTS版本的JDK
  • 并且设置JDK的环境变量

书中的例子只需要在 terminal下 cd 到对应的例子目录就可以直接运行,命令如下:

1
2
$ javac -cp "jars/*" step_definitions/CheckoutSteps.java
$ java -cp "jars/*:." cucumber.api.cli.Main -p progress --snippets camelcase -g step_definitions features

javac -cp "jars/*" step_definitions/CheckoutSteps.java 就是编译 step_definitions/CheckoutSteps.java 文件.
java -cp 即 -classpath , 指定 classpath 目录后,jvm 就会去对应目录寻找 Java 类.
cucumber.api.cli.Main 就是我们要执行的程序的入口.
-p pretty 就是告诉 cucumber 使用 pretty format 来显示输出结果.
--snippets camelcase 产生 CamelCase风格的 snippets,可以用参数 underscore生成 snake_case 风格的 snippets.
-g step_definitions 告诉 cucumber 去 step_definitions 目录下寻找对应的steps.
features 是指到当前目录下的 features 目录去寻找 .feature 文件.

可以先跳到 第十四章 去看完整的命令和参数描述:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
❯ java cucumber.api.cli.Main --help
Usage: java cucumber.api.cli.Main [options] [[[FILE|DIR][:LINE[:LINE]*] ]+ | @FILE ]

Options:

    -g, --glue PATH                        Where glue code (step definitions and hooks) is loaded from.
    -p, --plugin PLUGIN[:PATH_OR_URL]      Register a plugin.
                                           Built-in PLUGIN types: junit, html, pretty, progress, json, usage,
                                           rerun. PLUGIN can also be a fully qualified class name, allowing
                                           registration of 3rd party plugins.
    -f, --format FORMAT[:PATH_OR_URL]      Deprecated. Use --plugin instead.
    -t, --tags TAG_EXPRESSION              Only run scenarios tagged with tags matching TAG_EXPRESSION.
    -n, --name REGEXP                      Only run scenarios whose names match REGEXP.
    -d, --[no-]-dry-run                    Skip execution of glue code.
    -m, --[no-]-monochrome                 Don't colour terminal output.
    -s, --[no-]-strict                     Treat undefined and pending steps as errors.
        --snippets [underscore|camelcase]  Naming convention for generated snippets. Defaults to underscore.
    -v, --version                          Print version.
    -h, --help                             You're looking at it.
    --i18n LANG                            List keywords for in a particular language
                                           Run with "--i18n help" to see all languages

Feature path examples:
    <path>                                 Load the files with the extension ".feature" for the directory <path>
                                           and its sub directories.
    <path>/<name>.feature                  Load the feature file <path>/<name>.feature from the file system.
    classpath:<path>/<name>.feature        Load the feature file <path>/<name>.feature from the classpath.
    <path>/<name>.feature:3:9              Load the scenarios on line 3 and line 9 in the file 
                                           <path>/<name>.feature.
    @<path>/<file>                         Parse <path>/<file> for feature paths generated by the rerun formatter.

Regular Expression

正则表达式,做完就忘,触之google。

1
2
3
4
@Given("^the price of a \"(.*?)\" is (\\d+)c$")
public void thePriceOfAIsC(String name, int price) throws Throwable {
  int bananaPrice = price;
}

$ 匹配输入字符串的结尾位置

^ 匹配输入字符串的开始位置

\ 将下一个字符标记为原义字符, \" 匹配 ", \\ 匹配 \

+ 加号代表前面的字符必须至少出现一次(1次或多次)

\d 是匹配一个数字(0到9)

\d+ 表示一个或者多个数字

"\\d+" 是在程序字符串 "" 中,先转意\\\, 然后组合出\d+

. 匹配除换行符 \n 之外的任何单字符

"(.*?)" 则是得到包含几个元素的列表,每个元素直接对应原来文本中不同的位置匹配的项

Another Alternative: Cucumber Expression

正则表达式太过繁琐,Cucumber本身提供了一种Cucumber Expression,{word},{string}等等

1
2
3
4
5
6
7
8
9
Feature: Landing in page

Scenario: Login my account
    Given I have previously created a username: kay
    And I have previously created a password: okay
    When I enter my username correctly
    And I enter my passwrod correctly
    And I click on the button "login"
    Then I got a feedback "login successfully"

这里我用最近用nodejs写的一段BDD代码来举例几个用法,注意字符串和字面量之间的区别

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

Given("I have previously created a username: {word}", function (username) {
  this.username = username;
});

Given("I have previously created a password: {word}", function (password) {
  this.password = password;
});

...

When("I click on the button {string}", function (button) {
  if (button === "login") {
    this.isSubmit = true;
  } else {
    this.isSubmit = false;
  }
});

类似的用法还有{int},{float}等等,更多可以参考

https://github.com/cucumber/cucumber-expressions#readme

i18n

这本书对新手的不友好程度我感觉有五颗星吧,不只是库很旧,连java命令有些都给不全,新手几乎不太可能一直跟着书的命令跑下去,需要点前期的准备:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#在原书附带的代码里面的那个first_taste/jars 下有本书以来的Jar包添加到 CLASSPATH 里面export CLASSPATH=.:./jars/cucumber-core-1.2.0.jar:./jars/cucumber-java-1.2.0.jar:./jars/cucumber-jvm-deps-1.0.3.jar:./jars/gherkin-2.12.2.jar:./jars/junit-4.11.jar 

❯ java cucumber.api.cli.Main --i18n help
ar
bg
bm
ca
...
uz
vi
zh-CN
zh-TW
# 完整的命令是这样
❯ java -cp ".:jars/*" cucumber.api.cli.Main --i18n help

理解了上面怎么玩的后,我们可以自己DIY一个高级版, 新建一个叫 cucumber的文件,内容如下:

1
2
#!/bin/sh
java cucumber.api.cli.Main $1 $2

之后就能运行了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ chmod a+x cucumber
❯ ./cucumber --i18n help
ar
bg
bm
ca
cs
...
hr
hu
id
zh-CN
zh-TW

大概做两件事情

  • 把Jar包加入到 CLASSPATH 里面
  • 用一个Shell Script来接收命令参数并传给 java cucumber.api.cli.Main 执行

New Born : From info.cukes To io.cucumber

如果遇到编译上的兼容问题,请回想起来 info.cukes has deprated and migrated to io.cucumber.

  • 试试官方的 10 minutes tutorial, 就能得到一个使用了io.cucumber + junit 4的maven工程方便后续改代码了

  • 也可以试试我使用了io.cucumber 新库改的 Cucumber for Java Book 上的 pom.xml, 运行mvn package 后就会copy所有依赖的Jar在target/lib下,用这些新库去运行书上的例子就不会有那些奇奇怪怪的问题了

需要注意的是原书代码上的 import 和 Main 入口需要改成新库的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// import cucumber.api.java.en.*;
// import cucumber.api.PendingException;

import io.cucumber.java.PendingException;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;


  
// cucumber.api.cli.Main
io.cucumber.core.cli.Main

_To be Continued …_这个人很懒,有缘再续写

Reference

info.cukes » cucumber-java
https://mvnrepository.com/artifact/info.cukes/cucumber-java

Cucumber Expression
https://github.com/cucumber/cucumber-expressions#readme

Cucumber official guide.
https://cucumber.io/docs/guides/

10 minutes official tutorial of cucumber.
https://cucumber.io/docs/guides/10-minute-tutorial/?lang=java

Gist of new pom.xml using io.cucumber.
https://gist.github.com/qzi/37813ad453b38867035729b00224c274


Copyright Notice: This page is licensed under BY-NC-ND