Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sustainable BDD

Sustainable BDD

The BDD hype cycle is over. Recently, there’s been a lot of backlash against popular BDD libraries like Cucumber. Some developers blame their test frameworks for brittle test suites and long build times. Others go so far as to claim that acceptance testing is simply not sustainable, period. In this talk, we’ll do some root cause analysis of this phenomenon with shocking results - it’s not the test framework, it’s not the methodology, it’s you. You’ve abused your test framework, you’ve cargo-culted the methodology, and now you’re feeling the pain. We’ll show you a way out of the mess you’ve made. We’ll discuss the main problems BDD was intended to solve. We’ll show you how to groom your test suite into journey, functional, integration, and unit tests in order to address build times. We’ll teach how to mitigate against brittleness and flickers, and how to let your tests reveal the intent of the application and actually become the executable documentation we’ve been waiting for.

Robbie Clutton

February 23, 2013
Tweet

More Decks by Robbie Clutton

Other Decks in Technology

Transcript

  1. SUSTAINABLE
    BDD
    Matt Parker
    Robbie Clutton
    Software Engineers, Pivotal Labs

    View full-size slide

  2. WTF BDD
    1. Brittle Tests
    2. Dev-written Acceptance Criteria
    3. Unreadable Tests
    4. Flickering Tests
    5. Slow Tests

    View full-size slide

  3. CARGO CULTING

    View full-size slide

  4. HISTORY
    In the beginning... there
    was the unit test.
    And lo it was good.

    View full-size slide

  5. HISTORY
    sUnit -> xUnit

    View full-size slide

  6. HISTORY
    And then there was Dan
    North.

    View full-size slide

  7. SPECIFICATION
    BY EXAMPLE

    View full-size slide

  8. FEATURE: SIGN UP

    View full-size slide

  9. FEATURE: SIGN UP
    Give me an example.

    View full-size slide

  10. FEATURE: SIGN UP
    Give me an example.
    "User opens app for the first time,
    provides their email, desired
    username, and desired password, and
    is immediately logged in and allowed
    to start using the application."

    View full-size slide

  11. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password

    View full-size slide

  12. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password
    • Blank username, email, or password

    View full-size slide

  13. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password
    • Blank username, email, or password
    • Non-matching Password

    View full-size slide

  14. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password
    • Blank username, email, or password
    • Non-matching Password
    • Password too weak

    View full-size slide

  15. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password
    • Blank username, email, or password
    • Non-matching Password
    • Password too weak
    • Email address invalid

    View full-size slide

  16. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password
    • Blank username, email, or password
    • Non-matching Password
    • Password too weak
    • Email address invalid
    • Username unavailable

    View full-size slide

  17. FEATURE: ACCOUNT
    RECOVERY
    Background: User forgot password
    • User remembers email
    • User remembers username
    • User forgot everything

    View full-size slide

  18. FEATURE: SIGN UP
    Give me an example.
    • Valid username, email, password
    • Blank username, email, or password
    • Non-matching Password
    • Password too weak
    • Email address invalid
    • Username unavailable
    • User provides someone else’s email

    View full-size slide

  19. FEATURE: EMAIL
    VERIFICATION
    Give me an example.
    • Verification link followed
    • Fraud link followed
    • No links ever followed

    View full-size slide

  20. FEATURE: SIGN IN
    Give me an example.
    • Existing username / password
    • Unknown username
    • Wrong password

    View full-size slide

  21. HIDDEN
    COMPLEXITY
    1 sentence =>
    4 features =>
    17 scenarios

    View full-size slide

  22. HISTORY
    JBehave

    View full-size slide

  23. HISTORY
    Business Value
    Feature
    Scenario

    View full-size slide

  24. HISTORY
    RBehave

    View full-size slide

  25. HISTORY
    Story Runner / RSpec

    View full-size slide

  26. HISTORY
    Cucumber

    View full-size slide

  27. WHY GHERKIN?
    1. State Diagrams for the Masses

    View full-size slide

  28. APP AS STATE MACHINE
    Every feature of your application is a
    state transition.
    GIVEN (start state)
    WHEN (event)
    THEN (state transition)

    View full-size slide

  29. WHY GHERKIN?
    1. State Diagrams for the Masses
    2. Copy/Paste from Tracker to editor

    View full-size slide

  30. WHY GHERKIN?
    1. State Diagrams for the Masses
    2. Copy/Paste from Tracker to editor
    3. Living / Executable Documentation

    View full-size slide

  31. HISTORY
    Spinach, Turnip, Steak,
    Bacon, Filet,
    Lemon, ......................

    View full-size slide

  32. HISTORY
    10 build shiny new tool
    20 make same mistakes
    30 goto 10

    View full-size slide

  33. BRITTLE
    A single change in the application
    causes many tests to break

    View full-size slide

  34. BRITTLE
    A single change in the application
    causes many tests to break, and you
    are forced to visit each and every test
    and fix each one individually.

    View full-size slide

  35. Brittle tests
    typically don’t
    reveal intent.

    View full-size slide

  36. Feature: Tweet
    Scenario: Valid Tweet
    #....
    Scenario: Overlong Tweet
    #....
    Scenario: Duplicate tweet
    #....

    View full-size slide

  37. Scenario: Valid Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see "Your tweet was submitted"
    When I visit "/bob"
    Then I should see "this is a test tweet noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise haha I'm less than 140 characters."
    When I click "Log out"
    And I visit "/"
    And I fill in "username" with "alice"
    And I fill in "password" with "password"
    And I click "Log in"
    Then I should see "this is a test tweet noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise haha I'm less than 140 characters."

    View full-size slide

  38. Scenario: Valid Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see "Your tweet was submitted"
    When I visit "/bob"
    Then I should see "this is a test tweet noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise haha I'm less than 140 characters."
    When I click "Log out"
    And I visit "/"
    And I fill in "username" with "alice"
    And I fill in "password" with "password"
    And I click "Log in"
    Then I should see "this is a test tweet noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise haha I'm less than 140 characters."
    Login

    View full-size slide

  39. Scenario: Valid Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see "Your tweet was submitted"
    When I visit "/bob"
    Then I should see "this is a test tweet noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise haha I'm less than 140 characters."
    When I click "Log out"
    And I visit "/"
    And I fill in "username" with "alice"
    And I fill in "password" with "password"
    And I click "Log in"
    Then I should see "this is a test tweet noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise haha I'm less than 140 characters."
    Tweet

    View full-size slide

  40. Scenario: Overlong Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that
    follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise
    noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise OH NOEZ IM TOO LONG
    NOOOOOOOOOOOOOOOOOOOOOO"
    And I click "Submit"
    Then I should see "Tweet too long"

    View full-size slide

  41. Scenario: Overlong Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that
    follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise
    noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise OH NOEZ IM TOO LONG
    NOOOOOOOOOOOOOOOOOOOOOO"
    And I click "Submit"
    Then I should see "Tweet too long"
    Tweet

    View full-size slide

  42. Scenario: Overlong Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that
    follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise
    noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise OH NOEZ IM TOO LONG
    NOOOOOOOOOOOOOOOOOOOOOO"
    And I click "Submit"
    Then I should see "Tweet too long"
    Login

    View full-size slide

  43. Scenario: Duplicate Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see "Your tweet was submitted"
    When I visit "/"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see “Your tweet is a duplicate”
    When I visit "/bob"
    Then I should not see "this is a test tweet noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise OH NOEZ IM TOO LONG NOOOOOOOOOOOOOOOOOOOOOO"
    When I visit "/bob"
    Then I should not see "this is a test tweet noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise haha I'm less than 140 characters."

    View full-size slide

  44. Scenario: Duplicate Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see "Your tweet was submitted"
    When I visit "/"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see “Your tweet is a duplicate”
    When I visit "/bob"
    Then I should not see "this is a test tweet noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise OH NOEZ IM TOO LONG NOOOOOOOOOOOOOOOOOOOOOO"
    When I visit "/bob"
    Then I should not see "this is a test tweet noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise haha I'm less than 140 characters."
    Login

    View full-size slide

  45. Scenario: Duplicate Tweet
    Given there is a user "bob" with password "password"
    And there is a user "alice" with password "password" that follows "bob"
    And I visit "/"
    And I fill in "username" with "bob"
    And I fill in "password" with "password"
    And I click "Log in"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click "Submit"
    Then I should see "Your tweet was submitted"
    When I visit "/"
    When I fill in "Tweet" with "this is a test tweet noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise noise haha I'm less than 140 characters."
    And I click “Submit”
    Then I should see “Your tweet is a duplicate”
    When I visit "/bob"
    Then I should not see "this is a test tweet noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise OH NOEZ IM TOO LONG NOOOOOOOOOOOOOOOOOOOOOO"
    When I visit "/bob"
    Then I should not see "this is a test tweet noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise noise noise haha I'm less than 140 characters."
    Tweet

    View full-size slide

  46. feature “Tweet” do
    scenario "valid tweet" do
    create :user, username: "bob", password: "password"
    create :user, username: "alice", password: "password"
    visit "/"
    fill_in "Username", with: "bob"
    fill_in "Password", with: "password"
    click_button "Log In"
    fill_in "Tweet", with: "this is a test tweet noise noise noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise haha I'm less than 140 characters."
    click_button "Submit"
    page.should have_content "Your tweet was submitted"
    visit "/bob"
    page.should have_content "this is a test tweet noise noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise haha I'm less than 140 characters."
    click_link "Log out"
    visit "/"
    fill_in "Username", with: "alice"
    fill_in "Password", with: "password"
    click_button "Log in"
    page.should have_content "this is a test tweet noise noise noise noise noise noise noise noise noise noise noise
    noise noise noise noise noise haha I'm less than 140 characters."
    end
    end

    View full-size slide

  47. BIG RATS LEAVE BIG
    POOS

    View full-size slide

  48. BIG RATS LEAVE BIG
    POOS
    click_button "Tweet"

    View full-size slide

  49. BIG RATS LEAVE BIG
    POOS
    click_button "Tweet"
    click_link_or_button "Tweet"

    View full-size slide

  50. BIG RATS LEAVE BIG
    POOS
    click_button "Tweet"
    click_link_or_button "Tweet"
    click_on "Tweet"

    View full-size slide

  51. BIG RATS LEAVE BIG
    POOS
    click_button "Tweet"
    click_link_or_button "Tweet"
    click_on "Tweet"
    click_on "#tweet"

    View full-size slide

  52. BIG RATS LEAVE BIG
    POOS
    click_button "Tweet"
    click_link_or_button "Tweet"
    click_on "Tweet"
    click_on "#tweet"
    find("#tweet").click

    View full-size slide

  53. BIG RATS LEAVE BIG
    POOS
    click_button "Tweet"
    click_link_or_button "Tweet"
    click_on "Tweet"
    click_on "#tweet"
    find("#tweet").click
    page.execute_script("$('#tweet').click()")

    View full-size slide

  54. BIG RATS LEAVE BIG
    POOS
    “Now, whenever you tweet, pop up a modal
    that forces the user to enter a CAPTCHA.”

    View full-size slide

  55. BIG RATS LEAVE BIG
    POOS
    FFFFF...FFF....F....FFFFFFFFFFFFF.........

    View full-size slide

  56. BIG RATS LEAVE BIG
    POOS
    module WebHelpers
    def tweet(message)
    fill_in “Message”, with: message
    click_on “Tweet”
    end
    end

    View full-size slide

  57. BIG RATS LEAVE BIG
    POOS
    #rspec
    RSpec.configure do |c|
    c.include WebHelpers, type: :request
    end
    #cucumber
    World WebHelpers

    View full-size slide

  58. Feature: Tweet
    Scenario: Valid Tweet
    Given Bob has authenticated
    When Bob submits a valid tweet
    Then Alice can see Bob’s tweet
    And Bob should see that tweet in his timeline
    Scenario: Overlong Tweet
    #....
    Scenario: Duplicate tweet
    #....

    View full-size slide

  59. BIG RATS LEAVE BIG
    POOS
    Given /^Bob has authenticated$/ do
    visit “/”
    fill_in “Username”, with: “Bob”
    fill_in “Password”, with: “password”
    click_on “Sign In”
    end

    View full-size slide

  60. BIG RATS LEAVE BIG
    POOS
    Given /^Bob has authenticated$/ do
    login bob
    end

    View full-size slide

  61. BIG RATS LEAVE BIG
    POOS
    module WebHelpers
    #...
    def bob
    @bob ||= create :user, name: “Bob”
    end
    #...
    end

    View full-size slide

  62. BIG RATS LEAVE BIG
    POOS
    When /^Bob submits a valid Tweet$/ do
    click_on “Tweet”
    within(“#submit_tweet_modal”) do
    fill_in “Captcha”, with: captcha
    click_on “Tweet”
    end
    end

    View full-size slide

  63. BIG RATS LEAVE BIG
    POOS
    When /^Bob submits a valid Tweet$/ do
    tweet “Hello, world”
    end

    View full-size slide

  64. BIG RATS LEAVE BIG
    POOS
    Then /^Alice can see Bob’s Tweet$/ do
    click_on “Sign Out”
    fill_in “Username”, with: “Alice”
    fill_in “Password”, with: “password”
    click_on “Sign In”
    visit “/alice”
    page.should have_content “Hello, world”
    end

    View full-size slide

  65. BIG RATS LEAVE BIG
    POOS
    Then /^Alice can see Bob’s Tweet$/ do
    login alice
    alice.should see_tweet “Hello, world”
    end

    View full-size slide

  66. BIG RATS LEAVE BIG
    POOS
    feature “Tweet” do
    scenario “Valid Tweet” do
    login bob
    tweet “hello, world”
    login alice
    alice.should see “hello, world”
    end
    end

    View full-size slide

  67. PRODUCT OWNERS
    DON’T COLLABORATE
    ON ACCEPTANCE
    CRITERIA

    View full-size slide

  68. PRODUCT OWNERS
    DON’T COLLABORATE
    ON ACCEPTANCE
    CRITERIA
    1. CARGO CULT

    View full-size slide

  69. PRODUCT OWNERS
    DON’T COLLABORATE
    ON ACCEPTANCE
    CRITERIA
    1. CARGO CULT
    2. It’s hard

    View full-size slide

  70. PRODUCT OWNERS
    DON’T COLLABORATE
    ON ACCEPTANCE
    CRITERIA
    1. CARGO CULT
    2. It’s hard
    3. You’re annoying them

    View full-size slide

  71. Given I click on “Sign In”
    And I fill in “Username” with “Bob”
    And I fill in “Password” with “password”
    And I click on “Submit”
    #....

    View full-size slide

  72. Given Bob logged in

    View full-size slide

  73. NO ONE READS THE
    CUKES

    View full-size slide

  74. NO ONE READS THE
    CUKES
    1. They’re unreadable

    View full-size slide

  75. NO ONE READS THE
    CUKES
    1. They’re unreadable
    2. They’re not exposed anywhere

    View full-size slide

  76. $ cucumber -f html

    View full-size slide

  77. $ relish push myproj

    View full-size slide

  78. $ wally push \
    $PROJ features/

    View full-size slide

  79. NO ONE READS THE
    CUKES
    1. They’re unreadable
    2. They’re not exposed anywhere
    3. You haven’t needed documentation

    View full-size slide

  80. $ time rake
    real 18m0.926s

    View full-size slide

  81. $ time rake
    real 18m0.926s
    imagined ETERNITY

    View full-size slide

  82. Slow tests find
    a way of getting
    run less often.

    View full-size slide

  83. $ time rake
    real 18m0.926s
    imagined ETERNITY
    visit root_path
    click_on “Some Link”
    sleep 10
    page.should have_content “FOO”

    View full-size slide

  84. $ rake
    ..........F.........
    $ rake
    ...................
    $ rake
    ....F...........F....
    $ rake
    ...................

    View full-size slide

  85. $ time rake
    real 18m0.926s
    imagined ETERNITY
    visit root_path
    click_on “Some Link”
    wait_for { page.has_selector?
    (“.foo_container”) }
    page.should have_content “Foo”

    View full-size slide

  86. $ time rake
    real 18m0.926s
    imagined ETERNITY
    visit root_path
    click_on “Some Link”
    wait_for { page.should
    have_selector(“.foo_container”) }
    page.should have_content “Foo”
    Whoops

    View full-size slide

  87. $ time rake
    real 18m0.926s
    imagined ETERNITY
    visit root_path
    click_on “Some Link”
    page.should have_content “Foo”

    View full-size slide

  88. Q IS FOR QUARANTINE
    @quarantine
    Scenario: Something flakey
    #....

    View full-size slide

  89. Q IS FOR QUARANTINE
    #regular ci build
    #!/bin/bash
    cucumber --tags ~@quarantine

    View full-size slide

  90. Q IS FOR QUARANTINE
    #quarantine ci build
    #!/bin/bash
    cucumber --tags @quarantine

    View full-size slide

  91. BUILD NANNY
    1. Timebox
    2. Rethink
    3. Delete

    View full-size slide

  92. Photo by Kris Hicks

    View full-size slide

  93. $ time rake
    “You should not be afraid to delete tests that are no
    longer providing value, no matter whether you originally
    planned to keep them or not. We tend to treat tests as
    these holy creatures that live blameless, irreproachable
    lives once they have sprung into existence. Not so. The
    maintenance required to keep a test running weighs
    against its value in further development. Sometimes
    these lines cross, and the test simply becomes a burden
    on the project. Having the skill and experience to
    recognize a burdensome test is something we should be
    bringing to our clients, as well as the fortitude to
    rewrite it, rethink it, or delete it." - ADAM MILLIGAN

    View full-size slide

  94. $ time rake
    real 9m0.926s

    View full-size slide

  95. $ time rake
    real 9m0.926s
    imagined ETERNITY

    View full-size slide

  96. Why are you
    testing the
    login
    functionality 50
    times?

    View full-size slide

  97. 1. Journey
    2. Functional

    View full-size slide

  98. $ time rake
    real 9m0.926s
    imagined ETERNITY
    @authenticated
    Feature: Publishing Widgets
    Scenario: ....

    View full-size slide

  99. $ time rake
    real 9m0.926s
    imagined ETERNITY
    Before(“@authenticated”) do
    stub_authentication
    end

    View full-size slide

  100. $ time rake
    real 4m0.926s

    View full-size slide

  101. $ time rake
    real 4m0.926s
    imagined ETERNITY

    View full-size slide

  102. $ time rake
    Feature: Sign Up
    Scenario: Valid username, email, password
    Scenario: Blank username, email, or password
    Scenario: Non-matching Password
    Scenario: Password too weak
    Scenario: Email address invalid
    Scenario: Username unavailable
    Scenario: User provides someone else’s email

    View full-size slide

  103. $ time rake
    Feature: Sign Up
    Scenario: Valid username, email, password
    Scenario: Blank username, email, password
    Scenario: Non-matching Password
    Scenario: Password too weak
    Scenario: Email address invalid
    Scenario: Username unavailable
    Scenario: User provides someone else’s email

    View full-size slide

  104. $ time rake
    Feature: Sign Up
    Scenario: Valid username, email, password
    Scenario: Blank username, email, password
    Scenario: Non-matching Password
    Scenario: Password too weak
    Scenario: Email address invalid
    Scenario: Username unavailable
    Scenario: User provides someone else’s email

    View full-size slide

  105. $ time rake
    Feature: Sign Up
    Scenario: Valid username, email, password
    Scenario: Blank username, email, password
    @ci
    Scenario: Non-matching Password
    ....

    View full-size slide

  106. $ time rake
    #.cucumber.yml
    default: --tags ~@ci

    View full-size slide

  107. $ time rake
    Feature: Sign Up
    Scenario: Valid username, email, password
    Scenario: Blank username, email, password
    @no-ui
    Scenario: Non-matching Password
    ....

    View full-size slide

  108. $ time rake
    Before “@no-ui” do
    extend DomainHelpers
    end

    View full-size slide

  109. $ time rake
    module DomainHelpers
    attr_reader :current_user
    def authenticate
    @current_user = create :user
    end
    def tweet(message)
    current_user.tweet message
    end
    #...
    end

    View full-size slide

  110. $ time rake
    real 30.926s

    View full-size slide

  111. 1.
    FIGHT PAIN
    2.
    REVEAL INTENT
    3.
    BDD LIKE YOU
    MEAN IT

    View full-size slide