DevTeach12-betterspecs

48
Writing Better Domain Oriented Cucumber Features Amir Barylko

description

Presentation done at Devteach/RubyTeach 2012

Transcript of DevTeach12-betterspecs

Page 1: DevTeach12-betterspecs

Writing BetterDomain Oriented

Cucumber FeaturesAmir Barylko

Page 2: DevTeach12-betterspecs

• Architect

• Developer

• Mentor

• Great cook

• The one who’s entertaining you for the next hour!

Who am I?Better Cucumber Features

Page 3: DevTeach12-betterspecs

Behavior Driven Dev.

TextText

The rSpec Book

Page 4: DevTeach12-betterspecs

•Delivering late or over budget

•Delivering the wrong thing

• Unstable in production

• Costly to maintain

Why Projects Fail?Amir Barylko - Better Cucumber Features

Page 5: DevTeach12-betterspecs

• Unit Testing

• Integration Testing

• Acceptance Testing

Improve QualityAmir Barylko - Better Cucumber Features

Page 6: DevTeach12-betterspecs

• Implementing an application

• by describing its behavior

• from the perspective of the stakeholder

BDDAmir Barylko - Better Cucumber Features

Page 7: DevTeach12-betterspecs

Outside In ApproachAmir Barylko - Better Cucumber Features

BDD TDD

Page 8: DevTeach12-betterspecs

•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

Page 9: DevTeach12-betterspecs

Cucumber

Page 10: DevTeach12-betterspecs

• Business readable DSL

• Flush out requirements

• Documentation

• Automated testing

• Used by Cucumber, SpecFlow, jBehave

Gherkin DSLAmir Barylko - Better Cucumber Features

Page 11: DevTeach12-betterspecs

• Feature

• Scenario

• Given

•When

• Then

• And

• But

Gherkin KeywordsAmir Barylko - Better Cucumber Features

Page 12: DevTeach12-betterspecs

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!

Page 13: DevTeach12-betterspecs

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

Page 14: DevTeach12-betterspecs

• Parse the feature

• Parse the scenario

• For each scenario

• Find a step implementation

• Execute the code

Running FeaturesAmir Barylko - Better Cucumber Features

Page 15: DevTeach12-betterspecs

•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

Page 16: DevTeach12-betterspecs

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!

Page 17: DevTeach12-betterspecs

Expressive Scenarios

Page 18: DevTeach12-betterspecs

• Readability

• Ubiquitous Language

• Consistent use of terminology

• Express natural business intent

• Avoid technical aspects

What we want?Amir Barylko - Better Cucumber Features

Page 19: DevTeach12-betterspecs

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"

Page 20: DevTeach12-betterspecs

• 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

Page 21: DevTeach12-betterspecs

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

Page 22: DevTeach12-betterspecs

• 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

Page 23: DevTeach12-betterspecs

Too abstract?Amir Barylko - Better Cucumber Features

Scenario: The whole system

Given the system exists

When I use it

Then it should work, perfectly

Page 24: DevTeach12-betterspecs

• 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

Page 25: DevTeach12-betterspecs

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

Page 26: DevTeach12-betterspecs

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

Page 27: DevTeach12-betterspecs

• 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”

Page 28: DevTeach12-betterspecs

• 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 |

Page 29: DevTeach12-betterspecs

• 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 |

Page 30: DevTeach12-betterspecs

• 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

Page 31: DevTeach12-betterspecs

• 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

Page 32: DevTeach12-betterspecs

• 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

Page 33: DevTeach12-betterspecs

• 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

Page 34: DevTeach12-betterspecs

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

Page 35: DevTeach12-betterspecs

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

Page 36: DevTeach12-betterspecs

• 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

Page 37: DevTeach12-betterspecs

• 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

Page 38: DevTeach12-betterspecs

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)

Page 39: DevTeach12-betterspecs

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

Page 40: DevTeach12-betterspecs

• 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

Page 41: DevTeach12-betterspecs

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

Page 42: DevTeach12-betterspecs

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

Page 43: DevTeach12-betterspecs

Nicer stepsAmir Barylko - Better Cucumber Features

Then /^I should see the complete list of projects$/ do projects_page.list.should == stored_projectsend

HelperPage Object

Page 44: DevTeach12-betterspecs

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

Page 45: DevTeach12-betterspecs

Summary

Page 46: DevTeach12-betterspecs

• 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

Page 47: DevTeach12-betterspecs

• Email: [email protected],

• Twitter : @abarylko

• Blog: http://orthocoders.com

•Website: http://maventhought.com

ResourcesAmir Barylko - Better Cucumber Features

Page 48: DevTeach12-betterspecs

Resources IIAmir Barylko - Better Cucumber Features