图片 1

cucumber是一款测试工具。可用于大多数主流编程语言。比如JAVA、JS、Ruby、C++、Lua、Android、Kotlin、C#/F#、PHP、Python、Go、Groovy、Scala等等。其中JAVA、JS、Ruby的代码托管在cucumber下。官方建议选择与生产代码相同的平台或编程语言的实现。本文主要是JAVA平台下的介绍教程。使用方法非常简单,创建一个mvn工程,在pom.xml文件引入以下依赖即可。

本文由 伯乐在线 –
algo31031
翻译。未经许可,禁止转载!
英文出处:Chris
Zetter。欢迎加入翻译组。

Evan
Light在Github上发布了他的Coulda项目,一个以Test::Unit为基础的测试框架,

<dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-java8</artifactId> <version>4.2.0</version> <scope>test</scope></dependency>

Chris
Zetter是FutureLearn产品组的一名开发者,他为我们讲述了自己的小组为了使功能测试兼具可维护性与可读性,在把Cucumber替换为RSpec之后是如何来编写测试的。

coulda,从它的名字你就可以猜到,它受到了Cucumber和Shoulda 的启发。coulda
是一个简单的internal
DSL,可以捕获到与Cucumber相同的信息,但与Cucumber不同的是,它并不没有使用Gherkin的功能,使用的是标准的方法调用。并且coulda强调的是方法的重新使用。

也可以根据骨架创建cucumber项目。

测试是建立与维护一个大型平台不可或缺的一部分。每当我们为FutureLearn这个平台增添新功能时,我们都会编写自动化的功能测试来记录这些新功能是如何运作的,并确保他们不运转时我们也能知晓。

require 'rubygems'require 'coulda'include CouldaFeature "feature name" do in_order_to "foo" as_a "bar" i_want_to "blech" def something end def expectation end Scenario "pending scenario" Scenario "another scenario" do Given "a pending prereq" When "something happens" do something end Then "expect something else" do expectation end end Scenario "that is live" do Given "foo" do; end When "bar" do; end Then "blech" do; end endend

我们首先使用cucumber- prototype
Maven插件创建一个新项目目录。打开终端,转到要创建项目的目录(比如本文是hellocucumber),运行以下命令:

令人爱恨交加的Cucumber

Cucumber是一款用来编写功能测试的常用工具,每当我们开启项目时它都是我们的不二选择。它可以让我们以用户的视角编写出高层级的行为驱动测试。

Ruby

Feature: Enrolment Scenario: Enrolling in a course Given there is a
course And I am logged in as a learner When I enrol on a course Then the
course should appear in ‘my courses’

1
2
3
4
5
6
Feature: Enrolment
  Scenario: Enrolling in a course
    Given there is a course
    And I am logged in as a learner
    When I enrol on a course
    Then the course should appear in ‘my courses’

我们乐于使用Cucumber因为它可以使根据用户故事编写测试变得简单易行,而且写完的测试通俗易懂。然而使用Cucumber也有些许不足之处。首先,我们已经在项目里使用了RSpec,再引入Cucumber意味着又要多维护一个测试框架;其次,由于两者的DSLs和测试运行器不同,在他们之间进行脑筋切换又会带来额外开销;最后,我们特别不喜欢Cucumber所使用的正则表达式,因为同Ruby的标准方法调用相比,它们使测试变得更加晦涩难懂。

coulda算得上是一个好公民。它为kernel添加了一个“Feature”方法,没有monkey-patching,coulda只有几百行代码,并且它可以很好的与其他任何测试工具结合使用。github上查看coulda详情:

mvn archetype:generate \ -DarchetypeGroupId=io.cucumber \ -DarchetypeArtifactId=cucumber-archetype \ -DarchetypeVersion=2.3.1.2 \ -DgroupId=hellocucumber \ -DartifactId=hellocucumber \ -Dpackage=hellocucumber \ -Dversion=1.0.0-SNAPSHOT \ -DinteractiveMode=false

编写更好的RSpec features

那么,我么该如何在不失测试可读性的前提下停用Cucumber呢?

我们已经开始使用RSpec
features来替代Cucumber,它们通常看起来会是这样:

Ruby

feature ‘Enrolment’ do scenario ‘Enrolling in a course’ do course =
FactoryGirl.create(:course) learner = FactoryGirl.create(:learner)
login_as learner visit course_path(course) find(‘.join’).click
expect(page).to have_content(‘Thanks for joining!’) visit ‘/’
expect(page).to have_main_header(‘My Courses’) expect(page).to
have_content(course.full_title) end end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
feature ‘Enrolment’ do
  scenario ‘Enrolling in a course’ do
    course = FactoryGirl.create(:course)
 
    learner = FactoryGirl.create(:learner)
    login_as learner
 
    visit course_path(course)
    find(‘.join’).click
    expect(page).to have_content(‘Thanks for joining!’)
 
    visit ‘/’
    expect(page).to have_main_header(‘My Courses’)
    expect(page).to have_content(course.full_title)
  end
end

它们总是趋于变得很长,使得难以辨明其究竟在测试些什么。而且难以区分诸如Arrange,
Act,
Assert(在Cucumber里又被称为’Given’、’When’和’Then’)这些部分。我们试过在代码中这些步骤里添加注释,但它们就和通常那些程序代码里的注释一样不尽如人意:一段时间之后这些注释就变得与实际代码不同步了。

一般来说,如果是在程序里别的地方写出这么长的方法,我们就会有所警觉,并且通常会采用提取方法的办法进行重构。何不也这么做呢?让我们依据Cucumber步骤的风格,把这些代码也提取成一个个方法吧。

Ruby

feature ‘Enrolment’ do scenario ‘Enrolling in a course’ do
given_there_is_a_course and_i_am_logged_in_as_a_learner
when_i_enrol_on_a_course
then_the_course_should_appear_in_my_courses end def
given_there_is_a_course @course = FactoryGirl.create(:course) end
def and_i_am_logged_in_as_a_learner @learner =
FactoryGirl.create(:learner) login_as @learner end def
when_i_enrol_on_a_course visit course_path(@course)
find(‘.join’).click expect(page).to have_content(‘Thanks for joining!’)
end def then_the_course_should_appear_in_my_courses visit ‘/’
expect(page).to have_main_header(‘My Courses’) expect(page).to
have_content(@course.full_title) end end

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
feature ‘Enrolment’ do
  scenario ‘Enrolling in a course’ do
    given_there_is_a_course
    and_i_am_logged_in_as_a_learner
    when_i_enrol_on_a_course
    then_the_course_should_appear_in_my_courses
  end
 
  def given_there_is_a_course
    @course = FactoryGirl.create(:course)
  end
 
  def and_i_am_logged_in_as_a_learner
    @learner = FactoryGirl.create(:learner)
    login_as @learner
  end
 
  def when_i_enrol_on_a_course
    visit course_path(@course)
    find(‘.join’).click
    expect(page).to have_content(‘Thanks for joining!’)
  end
 
  def then_the_course_should_appear_in_my_courses
    visit ‘/’
    expect(page).to have_main_header(‘My Courses’)
    expect(page).to have_content(@course.full_title)
  end
end

你应该得到如下结果:

我们有何发现

我们移除了全部的Cucumber功能测试,并把它们中大部分用新式的RSpec
features加以重写。这样一来即可保证拥有Cucumber所提供的优秀的可读性,又使得测试变得更加便于编写和维护。

我们做了一个慎重的决定,不把各个features文件里提取的方法进行复用,因为担心这么做会使得测试难于理解。我们发现在编写一个feature下的多条scenario时,会不自觉的就想要进行代码复用。

赞 收藏
评论

[INFO] Project created from Archetype in dir: hellocucumber/cucumber[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------

关于作者:algo31031

图片 2

(新浪微博:@韩冰403)

个人主页 ·
我的文章

切换到刚才运行命令创建的目录:

cd hellocucumber

在IntelliJ IDEA(或者eclipse都行)中打开项目:

  • 文件->打开…->(选择pom.xml)
  • 选择Open as Project

现在,您已经安装了一个简单的Cucumber项目。

mvn test

您应该看到如下内容:

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestNo features found at [classpath:hellocucumber]0 Scenarios0 Steps0m0.004sTests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.541 secResults :Tests run: 0, Failures: 0, Errors: 0, Skipped: 0[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------

Cucumber的输出告诉我们它没有找到任何可以运行的东西。

当我们使用Cucumber进行行为驱动开发时,我们使用具体的例子来指定我们希望软件做什么。Scenario是在生产代码之前编写的。它们以可执行规范的形式开始生命。随着生产代码的出现,场景扮演了事实文档和自动化测试的角色。在Cucumber中,一个example称为Scenario。Scenario定义在.feature文件中,这些文件存储在src/test/resources/hellocucumber目录中。一个具体的例子就是:星期天不是星期五。创建一个名为src/test/resources/hellocucumber/is_it_friday_yet.feature的文件,文件包括以下内容:

Feature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday Given today is Sunday When I ask whether it's Friday yet Then I should be told "Nope"

这个文件的第一行以关键字”’Feature”’开始:后面跟着一个名称。最好使用与文件名类似的名称。第二行是对该特性的简要描述。Cucumber并不执行这一行,它只是一个文档。第4行,场景:Sunday
is not
Friday是一个scenario,它是说明软件应该如何工作的具体示例。最后三行以Given开头,WhenThen是我们的场景的步骤。这就是Cucumber将要执行的操作。

现在我们有了一个场景,我们可以让Cucumber执行它:

mvn test

Cucumber告诉我们有一个undefined的场景和三个undefined的步骤。它还建议我们使用一些代码片段来define这些步骤:

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Sunday # null When I ask whether it's Friday yet # null Then I should be told "Nope" # null1 Scenarios (1 undefined)3 Steps (3 undefined)0m0.040sYou can implement missing steps with the snippets below:@Given("^today is Sunday$")public void today_is_Sunday() { // Write code here that turns the phrase above into concrete actions throw new PendingException();}@When("^I ask whether it's Friday yet$")public void i_ask_whether_it_s_Friday_yet() { // Write code here that turns the phrase above into concrete actions throw new PendingException();}@Then("^I should be told \"\"$")public void i_should_be_told(String arg1) { // Write code here that turns the phrase above into concrete actions throw new PendingException();}

复制以上未定义步骤的三个代码片段:

@Given("^today is Sunday$")public void today_is_Sunday() { // Write code here that turns the phrase above into concrete actions throw new PendingException();}@When("^I ask whether it's Friday yet$")public void i_ask_whether_it_s_Friday_yet() { // Write code here that turns the phrase above into concrete actions throw new PendingException();}@Then("^I should be told \"\"$")public void i_should_be_told(String arg1) { // Write code here that turns the phrase above into concrete actions throw new PendingException();}

并将它们粘贴到src/test/java/hellocucumber/steps.java中。

再次运行Cucumber: mvn test。这次的输出略有不同:

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Sunday # Stepdefs.today_is_Sunday() cucumber.api.PendingException: TODO: implement me at hellocucumber.Stepdefs.today_is_Sunday(Stepdefs.java:12) at ✽.today is Sunday(hellocucumber/is_it_friday_yet.feature:5) When I ask whether it's Friday yet # Stepdefs.i_ask_whether_it_s_Friday_yet() Then I should be told "Nope" # Stepdefs.i_should_be_told1 Scenarios (1 pending)3 Steps (2 skipped, 1 pending)0m0.188scucumber.api.PendingException: TODO: implement me at hellocucumber.Stepdefs.today_is_Sunday(Stepdefs.java:12) at ✽.today is Sunday(hellocucumber/is_it_friday_yet.feature:5)

Cucumber找到我们的步骤定义并执行它们。它们当前被标记为pending,这意味着我们需要让它们做一些有用的事情。

下一步是按照步骤定义中的注释所告诉我们的去做:

Write code here that turns the phrase above into concrete actions

尝试在代码中使用与步骤中相同的单词。将步骤定义代码更改为:

package hellocucumber;import cucumber.api.java.en.Given;import cucumber.api.java.en.When;import cucumber.api.java.en.Then;import static org.junit.Assert.*;class IsItFriday { static String isItFriday(String today) { return null; }}public class Stepdefs { private String today; private String actualAnswer; @Given("^today is Sunday$") public void today_is_Sunday() { today = "Sunday"; } @When("^I ask whether it's Friday yet$") public void i_ask_whether_is_s_Friday_yet() { actualAnswer = IsItFriday.isItFriday; } @Then("^I should be told \"\"$") public void i_should_be_told(String expectedAnswer) { assertEquals(expectedAnswer, actualAnswer); }}

再次运行mvn test :

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Sunday # Stepdefs.today_is_Sunday() When I ask whether it's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "Nope" # Stepdefs.i_should_be_told java.lang.AssertionError: expected:<Nope> but was:<null> at org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834) at org.junit.Assert.assertEquals(Assert.java:118) at org.junit.Assert.assertEquals(Assert.java:144) at hellocucumber.Stepdefs.i_should_be_told(Stepdefs.java:30) at ✽.I should be told "Nope"(hellocucumber/is_it_friday_yet.feature:7)Failed scenarios:hellocucumber/is_it_friday_yet.feature:4 # Sunday isn't Friday1 Scenarios 3 Steps (1 failed, 2 passed)0m0.404s

这就是进步! 但前两步已经passing,最后一步却failing了。

让我们做最简单的事情来让场景通过。在本例中,这只是为了让我们的方法返回Nope
:

static String isItFriday(String today) { return "Nope";}

再次运行mvn test:

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Sunday # Stepdefs.today_is_Sunday() When I ask whether it's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "Nope" # Stepdefs.i_should_be_told1 Scenarios 3 Steps 0m0.255s

恭喜你!这是第一个绿色的Cucumber Scenario。

下一件要测试的事情是,我们也会在周五得到正确的结果。更新is-it-friday-yet.feature文件:

Feature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday Given today is Sunday When I ask whether it's Friday yet Then I should be told "Nope" Scenario: Friday is Friday Given today is Friday When I ask whether it's Friday yet Then I should be told "TGIF"

我们需要添加一个步骤定义,将today设置为“Friday”:

@Given("^today is Friday$")public void today_is_Friday() { this.today = "Friday";}

当我们运行这个测试时,它将失败。

Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday # hellocucumber/isitfriday.feature:4 Given today is "Sunday" # Stepdefs.today_is When I ask whether is's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "Nope" # Stepdefs.i_should_be_told Scenario: Friday is Friday # hellocucumber/is_it_friday.feature:9 Given today is "Friday" # Stepdefs.today_is When I ask whether is's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "TGIF" # Stepdefs.i_should_be_told org.junit.ComparisonFailure: expected:<[TGIF]> but was:<[Nope]> at org.junit.Assert.assertEquals(Assert.java:115) at org.junit.Assert.assertEquals(Assert.java:144) at hellocucumber.Stepdefs.i_should_be_told(Stepdefs.java:26) at ✽.I should be told "TGIF"(hellocucumber/is_it_friday.feature:12)org.junit.ComparisonFailure:Expected :TGIFActual :Nope <Click to see difference> at org.junit.Assert.assertEquals(Assert.java:115) at org.junit.Assert.assertEquals(Assert.java:144) at hellocucumber.Stepdefs.i_should_be_told(Stepdefs.java:26) at ✽.I should be told "TGIF"(hellocucumber/is_it_friday.feature:12)

那是因为我们还没有实现逻辑!我们接着做。

让它通过

我们应该更新我们的语句来实际评估Today是否等于“Friday”

static String isItFriday(String today) { if (today.equals) { return "TGIF"; } return "Nope";}

再次运行mvn test:

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Friday is Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Friday # Stepdefs.today_is_Sunday() When I ask whether it's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "TGIF" # Stepdefs.i_should_be_told Scenario: Sunday isn't Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Sunday # Stepdefs.today_is_Sunday() When I ask whether it's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "Nope" # Stepdefs.i_should_be_told2 scenarios 6 steps 0m0.255s

所以,我们都知道一周中不止周日和周五。让我们更新我们的scenario以使用变量并评估更多的可能性。我们将使用变量和示例来计算星期五、星期天和其他任何时间!更新is-it-friday-yet.feature文件。注意,当我们开始使用多个Examples时,我们是如何从一个Scenario切换到Scenario Outline的。

Feature: Is it Friday yet? Everybody wants to know when it's Friday Scenario Outline: Today is or is not Friday Given today is "<day>" When I ask whether it's Friday yet Then I should be told "<answer>" Examples: | day | answer | | Friday | TGIF | | Sunday | Nope | | anything else! | Nope |

我们需要用一个以<day>为字符串的步骤定义来替换today is Sundaytoday is Friday的步骤定义。更新stepdefs.java文件如下:

package hellocucumber;import cucumber.api.java.en.Given;import cucumber.api.java.en.When;import cucumber.api.java.en.Then;import static org.junit.Assert.*;class IsItFriday { static String isItFriday(String today) { if (today.equals) { return "TGIF"; } return "Nope"; }}public class Stepdefs { private String today; private String actualAnswer; @Given("^today is \"\"$") public void today_is(String today) { this.today = today; } @When("^I ask whether it's Friday yet$") public void i_ask_whether_is_s_Friday_yet() { this.actualAnswer = IsItFriday.isItFriday; } @Then("^I should be told \"\"$") public void i_should_be_told(String expectedAnswer) { assertEquals(expectedAnswer, actualAnswer); }}

再次执行mvn test:

------------------------------------------------------- T E S T S-------------------------------------------------------Running hellocucumber.RunCucumberTestFeature: Is it Friday yet? Everybody wants to know when it's Friday Scenario Outline: Today is or is not Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is <day> # hellocucumber/is_it_friday_yet.feature:5 When I ask whether it's Friday yet # hellocucumber/is_it_friday_yet.feature:6 Then I should be told <answer> # hellocucumber/is_it_friday_yet.feature:7 Scenario: Sunday isn't Friday # hellocucumber/is_it_friday_yet.feature:4 Given today is Sunday # Stepdefs.today_is_Sunday() When I ask whether it's Friday yet # Stepdefs.i_ask_whether_is_s_Friday_yet() Then I should be told "Nope" # Stepdefs.i_should_be_told Examples: | day | answer | | "Friday" | "TGIF" | | "Sunday" | "Nope" | | "anything else!" | "Nope" |3 scenarios 9 steps 0m0.255s

现在我们有了工作代码,我们应该做一些重构:

  • 我们应该将isItFriday方法从测试代码移到生产代码中。
  • 我们可以在某个时候从步骤定义中提取helper方法,用于我们在几个地方使用的方法。

在这个简短的教程中,您已经了解了如何安装Cucumber,如何遵循BDD流程来开发一个非常简单的方法,以及如何使用该方法来评估多个场景!

英文原文:

admin 计算机前端

发表评论

电子邮件地址不会被公开。 必填项已用*标注