cucumber: automating the requirements language you already speak
DESCRIPTION
TRANSCRIPT
Automating the Requirements Language You Already Speak
Ben Mabey@bmabey
Developer
Product Manager
Tester
Designer/UX
Requirements
56% of all bugs are introduced in requirements. (CHAOS Report)
45% of functionality is never used
Only 20% makes up corefunctionality that is “Always” or “Often” used.
Feature Devotion
TextPlacing emphasis on features instead of
overall outcomehttp://martinfowler.com/bliki/FeatureDevotion.html
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
As an impatient buyerI want to refine my searchSo that I can find what I want quickly
As an impatient buyerI want to refine my searchSo that I can find what I want quickly
Feature: Advanced Search
token for conversation
Gherkin
Scenario: titleGiven [Context]When [Action]Then [Expected Outcome]
Scenario: titleGiven [Context]And [More Context]When [Action]And [Other Action]Then [Expected Outcome]But [Unexpected Outcome]
Scenario: search by directorGiven the store has movies directed by “Steven Spielberg”When I search for “Steven Spielberg”Then I should see all of the movies directed by “Steven Spielberg”
Scenario: no resultsGiven the store has no movies directed by “Steven Spielberg”When I search for “Steven Spielberg”Then I should see “Sorry, but no movies were found”
Advanced Gherkin
Scenario: search by director Given movies directed by "Steven Spielberg" are in stock When I search for "Spielberg" under "Director" Then I the search results should be "E.T., and Jaws"
Step Tables Scenario: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "Spielberg" under "Director" Then I the search results should be "E.T., and Jaws"
Scenario: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "Spielberg" under "Director" Then I should see the following table: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | E.T. | Steven Spielberg | 1982 |
Step Tables
Scenario: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "Spielberg" under "Director" Then I the search results should be "E.T., and Jaws"
Scenario: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "Spielberg" under "Director" Then I the search results should be "E.T., and Jaws"
I only want to change the search query and
results part...
Scenario Outlines Scenario Outline: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "<Director Query>" under "Director" Then I the search results should be "<Search Results>"
Examples: | Director Query | Search Results | | Steve | E.T., Jaws | | George | Dawn of the Dead, Star Wars | | Lucas | Star Wars |
"<Director Query>" "<Search Results>"
Examples: | Director Query | Search Results | | Steve | E.T., Jaws | | George | Dawn of the Dead, Star Wars | | Lucas | Star Wars |
Scenario OutlinesScenario Outline: search by director
Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "<Director Query>" under "Director" Then I the search results should be "<Search Results>"
Examples: | Director Query | Search Results | | Steve | E.T., Jaws | | George | Dawn of the Dead, Star Wars | | Lucas | Star Wars |
BackgroundFeature: Account Profile
Scenario: change password success Given I'm logged in ...
Scenario: update contact info Given I'm logged in ...
BackgroundFeature: Account Profile
Scenario: change password success ...
Scenario: update contact info ...
Background: Given I'm logged in
Multi-Line String Steps
Scenario: register successfully Given I am on on the registration page When I sign up as "Jojo Binks" Then I should receive the following email: """ Thanks for signing up Jojo!
Important information about here. """
# language: jaフィーチャ: 加算 バカな間違いを避けるために 数学オンチとして 2つの数の合計を知りたい
シナリオテンプレート: 2つの数の加算について 前提 <値1> を入力 かつ <値2> を入力 もし <ボタン> を押した ならば <結果> を表示
例: | 値1 | 値2 | ボタン | 結果 | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | | 0 | 40 | add | 40 |
So now what?
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
AutomatingGherkin
project_root/| `-- features
project_root/| `-- features |-- awesomeness.feature |-- greatest_ever.feature
project_root/| `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb
project_root/| `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb |-- step_definitions | |-- domain_concept_A.rb | `-- domain_concept_B.rb
Step
Given a widget
Step
Given a widgetGiven /^a widget$/ do #codes go hereend
Definition
When /^I search by director for "([^\"]*)"$/ do |director|end
When /^I search by director for "([^\"]*)"$/ do |director|end
Regexp Capture -> Yielded Variable
When /^I search by director for "([^\"]*)"$/ do |director| visit advanced_search_path fill_in "By Director", :with => director click_button "Search"end
Webrat, Webdriver, Watir, etc..
Not Just for Ruby
When /^I search by director for "([^\"]*)"$/ do |director|end
[Given(@"^I search by director for \"([^\"]*)\"$")]public void searchDirector(String director) {}
http://wiki.github.com/richardlawrence/Cuke4Nuke/
Gherkin
Gherkin Ragel
Gherkin Ragel
C, C++, Ruby, Java, .net, etc,
Gherkin Ragel
C, C++, Ruby, Java, .net, etc,
http://specflow.org/
http://wiki.github.com/aslakhellesoy/cuke4duke/
When("^I search by director for \"([^\"]*)\"$"{ String d ->}
When /^I search by director for "([^\"]*)"$/ do |director|end
When("^I search by director for \"([^\"]*)\"$"{ d: String =>}
@When("^I search by director for \"([^\"]*)\"$")public void searchDirector(String director) {}
(When #"^I search by director for \"([^\"]*)\"$" (fn [director] ))
Uhh... this just seems like more work for me.
Scenario Outline: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "<Director Query>" under "Director" Then I the search results should be "<Search Results>"
Examples: | Director Query | Search Results | | Steve | E.T., Jaws | | George | Dawn of the Dead, Star Wars | | Zombie guy | Dawn of the Dead | | Lucas | Star Wars |
Scenario: search by director on a full moon Given that it is a full moon ...
Scenario Outline: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "<Director Query>" under "Director" Then I the search results should be "<Search Results>"
Examples: | Director Query | Search Results | | Steve | E.T., Jaws | | George | Dawn of the Dead, Star Wars | | Zombie guy | Dawn of the Dead | | Lucas | Star Wars |
Scenario: search by director on a full moon Given that it is a full moon ...
You know when you are done.
You know where to begin and end.
You know when you broke something.
Outside-In
Model
Controller
Model
View
Controller
Model
View
Controller
Model
UI/UX
View
Controller
Model
UI/UX
Write Scenarios
Steps are pending
Write Step Definition
Go Down A Gear
RSpec, xUnit, etc
Write Unit Test
Make Unit Test Pass
REFACTOR!!
Where Are we?
Continue until...
REFACTORand
REPEAT
Next set of slides stolen from Aslak Hellesøy, creator of .
http://www.slideshare.net/aslak.hellesoy/cuke4duke-javazone-2009
Feature: code-breaker submits guess In order to make time pass when I'm alone As a player I want to play the against a machine
Scenario: all correct Given the secret code is "r g y c" When I guess "r g y c" Then the mark should be "bbbb"
submit_guess.feature
features
srcsubmit_guess.feature
test/java/codebreakerGameSteps.java
features
srcsubmit_guess.feature
test/java/codebreakerGameSteps.java
$ gem install cucumber$ cucumber features
<repositories> <repository> <id>cukes</id> <url>http://cukes.info/maven</url> </repository> </repositories>
<pluginRepositories> <pluginRepository> <id>cukes</id> <url>http://cukes.info/maven</url> </pluginRepository> </pluginRepositories>
<dependencies> <dependency> <groupId>cuke4duke</groupId> <artifactId>cuke4duke</artifactId> <version>0.3.0</version> </dependency> <dependency> <groupId>org.picocontainer</groupId> <artifactId>picocontainer</artifactId> <version>2.8.3</version> </dependency> </dependencies>
<plugin> <groupId>cuke4duke</groupId> <artifactId>cuke4duke-maven-plugin</artifactId> <configuration> <jvmArgs> <jvmArg> -Dcuke4duke.objectFactory= cuke4duke.internal.java.PicoFactory </jvmArg> </jvmArgs> <cucumberArgs> <cucumberArg>${basedir}/src/test/java</cucumberArg> </cucumberArgs> <gems> <gem>install cuke4duke --version x.y.x</gem> </gems> </configuration> </plugin>
features
srcsubmit_guess.feature
test/java/codebreakerGameSteps.java
$ mvn integration-test \ -Dcucumber.installGems=true
pom.xml
Feature: code-breaker submits guess In order to make time pass when I'm alone As a player I want to play the against a machine
Scenario: all correct # features/c..s.feature:6 Given the secret code is "r g y c" # features/c..s.feature:7 When I guess "r g y c" # features/c..s.feature:8 Then the mark should be "bbbb" # features/c..s.feature:9
1 scenario (1 undefined)3 steps (3 undefined)0m0.076s
Undefined Steps
You can implement step definitions for undefined steps with these snippets:
@Given("^the secret code is \"([^\"]*)\"$")@Pendingpublic void theSecretCodeIsRGYC_(String arg1) {}
@When("^I guess \"([^\"]*)\"$")@Pendingpublic void iGuessRGYC_(String arg1) {}
@Then("^the mark should be \"([^\"]*)\"$")@Pendingpublic void theMarkShouldBeBbbb_(String arg1) {}
Snippets
GameSteps.javapackage codebreaker;import cuke4duke.*;
public class CodeBreakerSteps { @Given("^the secret code is \"([^\"]*)\"$") @Pending public void theSecretCodeIs(String code) { }
@When("^I guess \"([^\"]*)\"$") @Pending public void iGuess(String guess) { }
@Then("^the mark should be \"([^\"]*)\"$") @Pending public void theMarkShouldBe(String mark) { }}
Feature: code-breaker submits guess In order to make time pass when I'm alone As a player I want to play the against a machine
Scenario: all correct # features/c..s.feature:6 Given the secret code is "r g y c" # public void theS..(..) TODO (Cucumber::Pending) f../c..s.feature:7:in `Given the secret code is "r g y c"' When I guess "r g y c" # public void iGue..(..) Then the mark should be "bbbb" # public void theM..(..)
1 scenario (1 pending)3 steps (2 skipped, 1 pending)0m0.079s
$ mvn integration-test
Given the secret code is "r g y c" # public void codebreaker.GameSteps .theSecretCodeIs(java.lang.String)
Location
Steps & Step Definitions
Given the secret code is "r g y c"Step == Method invocation
Steps & Step Definitions
Given the secret code is "r g y c"
@Given("^the secret code is \"([^\"]*)\"$")public void theSecretCodeIs(String code) { }
public class GameSteps { private Game game;
@Given("^the secret code is \"([^\"]*)\"$") public void theSecretCodeIs(String code) { game = new Game(code); }}
Implement Intention
Compilation failure
src/test/java/codebreaker/CodeBreakerSteps.java:[6,12] cannot find symbolsymbol : class Gamelocation: class codebreaker.CodeBreakerSteps
features
srcsubmit_guess.feature
main/java/codebreakerGame.java
test/java/codebreakerGameSteps.java
Game.java
package codebreaker; public class Game { public Game(String code) { }}
Feature: code-breaker submits guess In order to make time pass when I'm alone As a player I want to play the against a machine
Scenario: all correct # features/c..s.feature:6 Given the secret code is "r g y c" # public void theS..(..) When I guess "r g y c" # public void iGue..(..) TODO (Cucumber::Pending) f../c..s.feature:8:in `When I guess "r g y c"' Then the mark should be "bbbb" # public void theM..(..)
1 scenario (1 pending)3 steps (1 skipped, 1 pending, 1 passed)0m0.112s
$ mvn integration-test
Repeat process until...
Feature: code-breaker submits guess In order to make time pass when I'm alone As a player I want to play the against a machine
Scenario: all correct # features/c..s.feature:6 Given the secret code is "r g y c" # public void theS..(..) When I guess "r g y c" # public void iGue..(..) Then the mark should be "bbbb" # public void theM..(..)
1 scenario (1 passed)3 steps (3 passed)0m0.121s
$ mvn integration-test
Scenario: all correct Given the secret code is "r g y c" When I guess "r g y c" Then the mark should be "bbbb"
Scenario: 2 wrong pos, 2 correct Given the secret code is "r g y c" When I guess "r g c y" Then the mark should be "bbww"
More Scenarios
Scenario: all correct # features/c..s.feature:6 Given the secret code is "r g y c" # public void theS..(..) When I guess "r g y c" # public void iGue..(..) Then the mark should be "bbbb" # public void theM..(..)
Scenario: all correct # features/c..s.feature:6 Given the secret code is "r g y c" # public void theS..(..) When I guess "r g y c" # public void iGue..(..) Then the mark should be "bbbb" # public void theM..(..) org.junit.ComparisonFailure: expected:<bb[bb]> but was:<bb[ww]> (NativeException) codebreaker/CodeBreakerSteps.java:20:in `theMarkShouldBe' features/codebreaker_submits_guess.feature:14:in `Then the mark should be "bbww"'
1 scenario (1 failed, 1 passed)3 steps (1 failed, 3 passed)0m0.121s
Time to write the
algorithm...
Scenario: all correct Given the secret code is "r g y c" When I guess "r g y c" Then the mark should be "bbbb"
Scenario: 2 wrong pos, 2 correct Given the secret code is "r g y c" When I guess "r g c y" Then the mark should be "bbww"
DRY?
Scenario Outline: submit guess Given the secret code is "<code>" When I guess "<guess>" Then the mark should be "<mark>"
Examples: | code | guess | mark | | r g y c | r g y c | bbbb | | r g y c | r g c y | bbww | | r g y c | y r g c | bwww | | r g y c | c r g y | wwww |
Scenario Outline
More Tricks
Tags
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
cucumber --tags ~@proposed
Tag Exclusion
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
cucumber --tags @wip:2 --wip
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
cucumber --tags @wip:2 --wip
Limit scenarios in flow
@store @luceneFeature: Advanced Search ...
@wip Scenario: search by director ...
@proposed @pending_ui Scenario: items not carried ...
@nightly Scenario: long running ...
@third_party Scenario: search Amazon ...
cucumber --tags @wip:2 --wip
Limit scenarios in flow
Expect failure - Success == Failure
HooksBefore doend
After do |scenario|end
World doend
World(MyModule)World(HerModule)
Tagged HooksBefore('@im_special', '@me_too') do @icecream = trueend
@me_tooFeature: Lorem Scenario: Ipsum Scenario: Dolor
Feature: Sit @im_special Scenario: Amet Scenario: Consec
Acceptance Tests ==
End-to-End?
Slow
Fast
Slow
Fast
Integrated
Isolated
Slow
Fast
Integrated
Isolated
Slow
Fast
Integrated
Isolated
HtmlUnitV8
Slow
Fast
Integrated
Isolated
HtmlUnitV8
What about me?
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
Given... When...Then...
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
PRD
Dead
Living
As an impatient buyer
I want to refine my search
So that I can find what
I want quickly
PRD
Dead
Scenario Outline: search by director Given the following movies are in stock: | Title | Director | Year | | Jaws | Steven Spielberg | 1975 | | Star Wars | George Lucas | 1975 | | Dawn of the Dead | George Romero | 1978 | | E.T. | Steven Spielberg | 1982 | When I search for "<Director Query>" under "Director" Then I the search results should be "<Search Results>"
Examples: | Director Query | Search Results | | Steve | E.T., Jaws | | George | Dawn of the Dead, Star Wars | | Lucas | Star Wars |
Feature: Advanced Search As an impatient buyer I want to refine my search So that I can find what I want quickly
Gherkin Subtleties
Feature: Advanced Search As an impatient buyer I want to refine my search So that I can find what I want quickly
Feature: Advanced Search As an impatient buyer I want to refine my search So that I can find what I want quickly
Scenario: search by director Given movies directed by "Steven Spielberg" are in stock When I search by director for "Spielberg" Then I should see all of the movies directed by "Steven Spielberg"
Feature: Advanced Search As an impatient buyer I want to refine my search So that I can find what I want quickly
Scenario: search by director Given movies directed by "Steven Spielberg" are in stock When I search by director for "Spielberg" Then I should see all of the movies directed by "Steven Spielberg"
Declarative
Scenario: search by director Given movies directed by "Steven Spielberg" are in stock And I am on the "Advanced Search" page When I fill in "Spielberg" for "Director" And press "Submit" Then I should see all of the movies directed by "Steven Spielberg"
Feature: Advanced Search As an impatient buyer I want to refine my search So that I can find what I want quickly
Imperative
Scenario: search by director Given movies directed by "Steven Spielberg" are in stock And I am on the "Advanced Search" page When I fill in "Spielberg" for "Director" And press "Submit" Then I should see all of the movies directed by "Steven Spielberg"
Feature: Advanced Search As an impatient buyer I want to refine my search So that I can find what I want quickly
Imperative
I like it! I actually know how a user can use it!
Balance Abstraction
Scenario: successful login Given I'm on the login page When I fill in "jimmy" for "Login" And fill in "password" for "Password" And click "Login" Then I should see "Welcome back jimmy!"
Scenario: change password success
Scenario: change password success Given I'm on the login page When I fill in "jimmy" for "Login" And fill in "password" for "Password" And click "Login" Then I should see "Welcome back jimmy!"
Scenario: change password success Given I'm on the login page When I fill in "jimmy" for "Login" And fill in "password" for "Password" And click "Login" Then I should see "Welcome back jimmy!" When I click "Change Password" And fill in the following | Old Password | password | | New Password | brand-new | | Confirmation | brand-new | And click "Change Password" Then I should see "Your password has been changed."
Scenario: change password success When I click "Change Password" And fill in the following | Old Password | password | | New Password | brand-new | | Confirmation | brand-new | And click "Change Password" Then I should see "Your password has been changed."
Given I'm on the login page When I fill in "jimmy" for "Login" And fill in "password" for "Password" And click "Login" Then I should see "Welcome back jimmy!"
Incidental Details
Scenario: change password success Given I'm logged in When I click "Change Password" And fill in the following | Old Password | password | | New Password | brand-new | | Confirmation | brand-new | And click "Change Password" Then I should see "Your password has been changed."
Hide the noise!
Resourceshttp://cukes.info
http://blog.dannorth.net/whats-in-a-story/
http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
http://wiki.github.com/aslakhellesoy/cuke4duke/
http://blog.josephwilk.net/ruby/rocket-fuelled-cucumbers.html
Thanks!BenMabey.com
github.com/bmabey
Twitter: bmabey