Writing BetterDomain Oriented
Cucumber FeaturesAmir Barylko
• Architect
• Developer
• Mentor
• Great cook
• The one who’s entertaining you for the next hour!
Who am I?Better Cucumber Features
Behavior Driven Dev.
TextText
The rSpec Book
•Delivering late or over budget
•Delivering the wrong thing
• Unstable in production
• Costly to maintain
Why Projects Fail?Amir Barylko - Better Cucumber Features
• Unit Testing
• Integration Testing
• Acceptance Testing
Improve QualityAmir Barylko - Better Cucumber Features
• Implementing an application
• by describing its behavior
• from the perspective of the stakeholder
BDDAmir Barylko - Better Cucumber Features
Outside In ApproachAmir Barylko - Better Cucumber Features
BDD TDD
•Write a test before any line of code
•Write minimum amount of code to make the test pass
• Refactor code to eliminate “smells”
Test FirstAmir Barylko - Better Cucumber Features
Cucumber
• Business readable DSL
• Flush out requirements
• Documentation
• Automated testing
• Used by Cucumber, SpecFlow, jBehave
Gherkin DSLAmir Barylko - Better Cucumber Features
• Feature
• Scenario
• Given
•When
• Then
• And
• But
Gherkin KeywordsAmir Barylko - Better Cucumber Features
Feature: Listing projects As a user I Want to see the list of projects So I can choose one to see the details
Scenario: List all projects (steps here to implement scenario)
Scenario: No projects are available (steps here to implement scenario)
FeaturesAmir Barylko - Better Cucumber Features
Free text!
ScenarioAmir Barylko - Better Cucumber Features
Scenario: List all projects Given I'm logged in And I have some projects stored When I browse the projects Then I should see all of them listed
Step 1
Step 2
Step 3
Step 4
• Parse the feature
• Parse the scenario
• For each scenario
• Find a step implementation
• Execute the code
Running FeaturesAmir Barylko - Better Cucumber Features
•Matching regular expression
Matching StepAmir Barylko - Better Cucumber Features
Given I have some projects stored
Given /^I have some projects stored$/
Feature File
Step Def File
Given /^I have some projects stored$/ do
projects = 10.times { random_valid_project }
fake_response = create_response(projects)
FakeWeb.register_uri(....)
end
StepAmir Barylko - Better Cucumber Features
Plain Ruby!
Expressive Scenarios
• Readability
• Ubiquitous Language
• Consistent use of terminology
• Express natural business intent
• Avoid technical aspects
What we want?Amir Barylko - Better Cucumber Features
Imperative styleAmir Barylko - Better Cucumber Features
Scenario: Redirect user to originally requested page
Given a User "dave" exists with password "secret"
And I am not logged in
When I navigate to the home page
Then I am redirected to the login form
When I fill in "Username" with "dave"
And I fill in "Password" with "secret"
And I press "Login"
• Who needs the passwords?
• Tightly coupled to page implementation
• Lacks domain language
• Brittle tests
• Does not tell a story (boring)
What’s the problem?Amir Barylko - Better Cucumber Features
Declarative styleAmir Barylko - Better Cucumber Features
Scenario: Redirect user to originally requested page
Given I am an authenticated user
When I attempt to view restricted content
Then I am presented with a login form
When I authenticated with valid credentials
Then I should be shown the restricted content
• Imperative is associated to “how” to do it
•Declarative is associated to “what” we want
•Where’s the boundary?
Declarative vs ImperativeAmir Barylko - Better Cucumber Features
Too abstract?Amir Barylko - Better Cucumber Features
Scenario: The whole system
Given the system exists
When I use it
Then it should work, perfectly
• Steps may have some degree of repetition
• Because they start with the same “state”
• So they share the first X steps
Background stepsAmir Barylko - Better Cucumber Features
Scenario: Change Password
Given I am logged in
And I choose to change my password
When I enter a new password
Then my password should be changed
Scenario: Change Password with same credentials
Given I am logged in
And I choose to change my password
When I enter the same password
Then I should see an error message explaining the problem
Similar scenariosAmir Barylko - Better Cucumber Features
Background: I want to change my password
Given I am logged in
And I choose to change my password
Scenario: Change Password
When I enter a new password
Then my password should be changed
Scenario: Change Password with same credentials
When I enter the same password
Then I should see an error message explaining the problem
Create BackgroundAmir Barylko - Better Cucumber Features
• Sometimes data is hard to put in a step
• with multiple entries
Using TablesAmir Barylko - Better Cucumber Features
Scenario: Listing movies
Given the movie “Blazing saddles” released “7 Feb 1974”
And the movie “Young Frankenstein” released “15 Dec 1974”
And the movie “The Producers” released “10 Nov 1968”
• Express data in tabular form
That’s boring!Amir Barylko - Better Cucumber Features
Scenario: Listing movies
Given these movies:
| title | release |
| Blazing saddles | 7 Feb 1974 |
| Young Frankenstein | 15 Dec 1974 |
| The Producers | 10 Nov 1968 |
• Don’t use the header
Or just a listAmir Barylko - Better Cucumber Features
Scenario: Listing movies
Given these movies:
| Blazing saddles |
| Young Frankenstein |
| The Producers |
• Do you really need the list?
Why the detail though?Amir Barylko - Better Cucumber Features
Scenario: Listing movies
Given I have some movies stored
When I browse the list
Then I should see the complete collection
• Each scenario leaves the system in a particular state
• The state has to be cleaned up for the next scenario
• Otherwise it will “leak” into it
• One scenario should not depend on another
Leaky ScenariosAmir Barylko - Better Cucumber Features
• Use a framework to generate valid data
• FactoryGirl is a very good option
• FactoryGirl.create(:customer)
• FactoryGirl.create(:invalid_bank_accout)
• Faker will help you to generate fake data
Generating dataAmir Barylko - Better Cucumber Features
• Steps can have arguments
• Though regular expression they don’t always show intent
• And also we may need to “reuse” them
TransformsAmir Barylko - Better Cucumber Features
Steps with argumentsAmir Barylko - Better Cucumber Features
Given /^I search for a movie “([^"]*)”$/ do |name|
.... # some code here
end
Given /^I have a movie called “([^"]*)”$/ do |name|
.... # some code here
end
Capture the argumentAmir Barylko - Better Cucumber Features
Given /^I search for a movie “(#{MOVIE_NAME})”$/ do |name|
.... # some code here
end
MOVIE_NAME = Transform /^([^"]+)$/ do | movie_name |
movie_name.downcaseend
• Helpers are a great tool to encapsulate common functionality
• Or to help describe better our intention
• and to avoid looking at ugly code
HelpersAmir Barylko - Better Cucumber Features
• The World is created for each scenario
• Instance variables have to be set
• Instead we can use a helper method
• to store/create the resource
Current InstanceAmir Barylko - Better Cucumber Features
Helper ClassAmir Barylko - Better Cucumber Features
module ProjectHelper def current_project(project = nil) @current_project ||= project end def project_list_page @project_list_page ||= ProjectListPage.new end end
World(ProjectHelper)
Custom matchersAmir Barylko - Better Cucumber Features
RSpec::Matchers.define :match_stored_projects do match do |actual| actual == Project.all.map { ... } end
failure_message_for_should do |actual| "The projects in the page should match...\n" + "The page contains #{actual}' \n" + "But the storage contains #{@expected}" end
end
• The steps rely on the HTML implementation
• Searching for elements can be repetitive
• or ugly
• and not always show intention
Page ObjectsAmir Barylko - Better Cucumber Features
•
What can we do?Amir Barylko - Better Cucumber Features
Then /^I should see the complete list of projects$/ do actual = all(:css, "#projects tbody tr") .map { |tr| tr.all("td").map(&:text) } .map { |cells| ... } expected = Project.all.map { |p| ... } actual.should == expectedend
•
Abstraction!Amir Barylko - Better Cucumber Features
class ProjectListPage include PageObject def projects all(:css, "#projects tr"). drop(1). #drop the header map { |r| r.all(:css, 'td').map(&:text) }. map { |r| Project.new(...) } endend
•
Nicer stepsAmir Barylko - Better Cucumber Features
Then /^I should see the complete list of projects$/ do projects_page.list.should == stored_projectsend
HelperPage Object
•
With a custom matcherAmir Barylko - Better Cucumber Features
Then /^I should see the complete list of projects$/ do projects_page.should list_stored_projectsend
Custom Matcher
Summary
• Focus your scenarios on “what” not “how”
• Read about scenario outlines
• Follow “the Cucumber book” practices
• Learn more about page objects pattern
• Start with a simple project
Next stepsAmir Barylko - Better Cucumber Features
• Email: [email protected],
• Twitter : @abarylko
• Blog: http://orthocoders.com
•Website: http://maventhought.com
ResourcesAmir Barylko - Better Cucumber Features
Resources IIAmir Barylko - Better Cucumber Features