SlideShare a Scribd company logo
1 of 137
Download to read offline
Outside-In
                           Development
                               With




                            Ben Mabey
Saturday, March 14, 2009
Who’s my
                           audience?



Saturday, March 14, 2009
Saturday, March 14, 2009
Scenario: planning meeting
      Given I am a Software Developer
      When I am given a feature request
      Then I should...




Saturday, March 14, 2009
What is the most
                           CRITICAL
   word to keep in mind?



Saturday, March 14, 2009
W
Saturday, March 14, 2009
Scenario: planning meeting
      Given I am a Software Developer
      When I am given a feature request
      Then I should ask why.




Saturday, March 14, 2009
_why




Saturday, March 14, 2009
Why?
Saturday, March 14, 2009
Feature Request:
                      Print Reports
                Story from
                Thought works:

                http://
                www.theregister.co.uk/
                2007 /06/25/




Saturday, March 14, 2009
Saturday, March 14, 2009
Protect Revenue

                           Increase Revenue

                           Manage Cost


Saturday, March 14, 2009
* not executed
                           * documentation value

         Feature: title    * variant of contextra
                           * business value up front




         In order to [Business Value]
         As a [Role]
         I want to [Some Action] (feature)




Saturday, March 14, 2009
There is no template.
          What is important to
          have in narrative:

           * business value
           * stakeholder role
           * user role
           * action to be taken by
          user




Saturday, March 14, 2009
Scenario: title
              Given [Context]
              When I do [Action]
              Then I should see [Outcome]




Saturday, March 14, 2009
Scenario: title
              Given [Context]
              And [More Context]
              When I do [Action]
              And [Other Action]
              Then I should see [Outcome]
              But I should not see [Outcome]


Saturday, March 14, 2009
project_root/
        |
        `-- features




Saturday, March 14, 2009
project_root/
        |
        `-- features
            |-- awesomeness.feature
            |-- greatest_ever.feature




Saturday, March 14, 2009
project_root/
        |
        `-- features
            |-- awesomeness.feature
            |-- greatest_ever.feature
            `-- support
                |-- env.rb
                `-- other_helpers.rb




Saturday, March 14, 2009
project_root/
        |
        `-- features
            |-- awesomeness.feature
            |-- greatest_ever.feature
            `-- support
                |-- env.rb
                `-- other_helpers.rb
            |-- step_definitions
            |   |-- domain_concept_A.rb
            |   `-- domain_concept_B.rb

Saturday, March 14, 2009
Step

      Given a widget




Saturday, March 14, 2009
Step   Definition

                                  Given /^a widget$/ do
      Given a widget                #codes go here
                                  end




Saturday, March 14, 2009
Step         Definition
                                  Step Mother

                                        Given /^a widget$/ do
      Given a widget                      #codes go here
                                        end




Saturday, March 14, 2009
Step         Definition
                                  Step Mother

                                        Given /^a widget$/ do
      Given a widget                      #codes go here
                                        end




Saturday, March 14, 2009
Outside-In


Saturday, March 14, 2009
Saturday, March 14, 2009
Saturday, March 14, 2009
Write Scenarios




Saturday, March 14, 2009
Steps are pending




Saturday, March 14, 2009
Write Step Definition




Saturday, March 14, 2009
Go Down A Gear




Saturday, March 14, 2009
RSpec




Saturday, March 14, 2009
RSpec

                           We ain’t got
                            no RSPEC!


Saturday, March 14, 2009
RSpec, TestUnit, etc




Saturday, March 14, 2009
Write Code Example
              (Unit Test)




Saturday, March 14, 2009
Make Example Pass




Saturday, March 14, 2009
REFACTOR!!



Saturday, March 14, 2009
Where Are we?




Saturday, March 14, 2009
Continue until...




Saturday, March 14, 2009
REFACTOR
                              and
                            REPEAT
Saturday, March 14, 2009
features/manage_my_wishes.feature
      Feature: manage my wishes

           In order to get more stuff
           As a greedy person
           I want to manage my wish list for my family members to view

           Scenario: add wish
             Given I am logged in
             When I make a quot;New carquot; wish
             Then quot;New carquot; should appear on my wish list




Saturday, March 14, 2009
Saturday, March 14, 2009
Line # of scenario




Saturday, March 14, 2009
Saturday, March 14, 2009
I can has backtraze?
     Given I am logged in   #features/manage_my_wishes.feature:8




Saturday, March 14, 2009
Saturday, March 14, 2009
features/i_can_has_wishez_plz.feature


      OH HAI: I CAN HAS WISHEZ PLZ

           SO DAT I CAN HAS HUGS TIEM AN PLAY TIEM AN SLEEP TIEM AN NOM TIEM
           AS NICE GREEDEE KITTEH
           I CAN PLZ MANEGE TEH WISHEZ

           MISHUN: MAK NEW WISH
             GIVN I AM LOGGD IN
             WEN I MAK quot;CHEEZBURGERquot; WISH
             DEN I C quot;CHEEZBURGERquot; ON ME WISHEZ LIT




Saturday, March 14, 2009
Saturday, March 14, 2009
Saturday, March 14, 2009
cucumber some.feature --language en-lol




Saturday, March 14, 2009
cucumber some.feature --language en-lol




Saturday, March 14, 2009
Saturday, March 14, 2009
Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

      end



         Test Data Builder / Object Mother




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

      end



         Fixture Replacement, Fixjour, Factory Girl, etc
      spec/fixjour_builders.rb
      Fixjour do
        define_builder(User) do |klass, overrides|
          klass.new(
                     :email => quot;user#{counter(:user)}@email.comquot;,
                     :password => 'password',
                     :password_confirmation => 'password'
          )
        end
      end

Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
                           Webrat / Awesomeness
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
                           Webrat / Awesomeness
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
                           Webrat / Awesomeness
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end

      features/support/env.rb
      require 'webrat'

      Webrat.configure do |config|
        config.mode = :rails
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
                           Webrat / Awesomeness
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end

      features/support/env.rb
      require 'webrat'

      Webrat.configure do |config|
        config.mode = :rails
      end
                                            Adapter


Saturday, March 14, 2009
features/step_definitions/user_steps.rb
                           Webrat / Awesomeness
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end

      features/step_definitions/webrat_steps.rb
      When /^I press quot;(.*)quot;$/ do |button|
        click_button(button)
      end
                                            19 Steps Out-of-box
      When /^I follow quot;(.*)quot;$/ do |link|
        click_link(link)
      end

      When /^I fill in quot;(.*)quot; with quot;(.*)quot;$/ do |field, value|
        fill_in(field, :with => value)
      end
Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
        # make sure we have actually logged in- so we fail fast if not
        session[:user_id].should == @current_user.id
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
        # make sure we have actually logged in- so we fail fast if not
        session[:user_id].should == @current_user.id
        controller.current_user.should == @current_user
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        Specify outcome, not implementation.
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
        # make sure we have actually logged in- so we fail fast if not
        session[:user_id].should == @current_user.id
        controller.current_user.should == @current_user
        response.should contain(quot;Signed in successfullyquot;)
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
        # make sure we have actually logged in- so we fail fast if not
        response.should contain(quot;Signed in successfullyquot;)
      end




Saturday, March 14, 2009
Saturday, March 14, 2009
No route matches “/sessions/create” with
    {:method=>:post} (ActionController::RoutingError)




Saturday, March 14, 2009
I’m going to cheat...




Saturday, March 14, 2009
I’m going to cheat...
   $ gem install thoughtbot-clearance
   $ ./script generate clearance
   $ ./script generate clearance_features




Saturday, March 14, 2009
Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|

      end

      Then /^(.+) should appear on my wish list$/ do |wish|

      end




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|

      end

      Then /^(.+) should appear on my wish list$/ do |wish|

      end




                      Regexp Capture -> Yielded Variable




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|
        visit quot;/wishesquot;
        click_link quot;Make a wishquot;
        fill_in quot;Wishquot;, :with => wish
        click_button
      end

      Then /^(.+) should appear on my wish list$/ do |wish|

      end




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|
        visit quot;/wishesquot;
        click_link quot;Make a wishquot;
        fill_in quot;Wishquot;, :with => wish
        click_button
      end

      Then /^(.+) should appear on my wish list$/ do |wish|
        response.should contain(quot;Your wish has been added!quot;)
        response.should contain(wish)
      end




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|
        visit quot;/wishesquot;
        click_link quot;Make a wishquot;
        fill_in quot;Wishquot;, :with => wish
        click_button
      end
    No route matches “/wishes” with
    {:method=>:get} appear on my wish list$/ do |wish|
     Then /^(.+) should (ActionController::RoutingError)
        response.should contain(quot;Your wish has been added!quot;)
        response.should contain(wish)
      end




Saturday, March 14, 2009
config/routes.rb
      ActionController::Routing::Routes.draw do |map|

           map.resources :wishes




Saturday, March 14, 2009
config/routes.rb
      ActionController::Routing::Routes.draw do |map|

           map.resources :wishes



    When I make a “New car” wish
      uninitialized constant WishesController (NameError)




Saturday, March 14, 2009
config/routes.rb
      ActionController::Routing::Routes.draw do |map|

           map.resources :wishes




  $./script generate rspec_controller new create




Saturday, March 14, 2009
config/routes.rb
      ActionController::Routing::Routes.draw do |map|

           map.resources :wishes




      When I make a “New car” wish
       Could not find link with text or title or
       id “Make a wish” (Webrat::NotFoundError)




Saturday, March 14, 2009
app/views/wishes/index.html.erb
      <%= link_to quot;Make a wishquot;, new_wish_path %>




Saturday, March 14, 2009
app/views/wishes/index.html.erb
      <%= link_to quot;Make a wishquot;, new_wish_path %>




     When I make a “New car” wish
      Could not find field: “Wish” (Webrat::NotFoundError)




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|
        visit quot;/wishesquot;
        click_link quot;Make a wishquot;
        fill_in quot;Wishquot;, :with => wish
        click_button
      end




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|
        visit quot;/wishesquot;
        click_link quot;Make a wishquot;
        fill_in quot;Wishquot;, :with => wish
        click_button
      end


      app/views/wishes/new.html.erb
      <% form_for :wish do |f| %>
        <%= f.label :name, quot;Wishquot; %>
        <%= f.text_field :name %>
        <%= submit_tag quot;Make the wish!quot; %>
      <% end %>




Saturday, March 14, 2009
features/step_definitions/wish_steps.rb
      When /^I make a quot;(.+)quot; wish$/ do |wish|
        visit quot;/wishesquot;
        click_link quot;Make a wishquot;
        fill_in quot;Wishquot;, :with => wish
        click_button
      end


       Location Strategy FTW!
      app/views/wishes/new.html.erb
      <% form_for :wish do |f| %>
        <%= f.label :name, quot;Wishquot; %>
        <%= f.text_field :name %>
        <%= submit_tag quot;Make the wish!quot; %>
      <% end %>




Saturday, March 14, 2009
View

                           Controller



Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
      describe WishesController do
        describe quot;POST / (#create)quot; do


        end
      end




Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
      describe WishesController do
        describe quot;POST / (#create)quot; do

                it quot;should create a new wish for the user with the paramsquot; do
                  user = mock_model(User, :wishes => mock(quot;wishes associationquot;))
                  controller.stub!(:current_user).and_return(user)

                     user.wishes.should_receive(:create).with(wish_params)

            post :create, 'wish' => {'name' => 'Dog'}
          end
        end
      end




Saturday, March 14, 2009
app/controllers/wishes_controller.rb
      class WishesController < ApplicationController

           def create
             current_user.wishes.create(params['wish'])
           end

      end




Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
      describe WishesController do
        describe quot;POST / (#create)quot; do
          before(:each) do
            .....

                it quot;should redirect the user to their wish listquot; do
                  do_post
                  response.should redirect_to(wishes_path)
                end

        end
      end




Saturday, March 14, 2009
app/controllers/wishes_controller.rb
           def create
             current_user.wishes.create(params['wish'])
             redirect_to :action => :index
           end




Saturday, March 14, 2009
View

                           Controller

                             Model

Saturday, March 14, 2009
app/controllers/wishes_controller.rb
           def create
             current_user.wishes.create(params['wish'])
             redirect_to :action => :index
           end



    When I make a “New car” wish
      undefined method `wishes` for #<User:0x268e898>
      (NoMethodError)




Saturday, March 14, 2009
app/controllers/wishes_controller.rb
           def create
             current_user.wishes.create(params['wish'])
             redirect_to :action => :index
           end


   $./script generate rspec_model wish
   name:string user_id:integer




Saturday, March 14, 2009
app/models/wish.rb
      class Wish < ActiveRecord::Base
        belongs_to :user
      end



      app/models/user.rb
      class User < ActiveRecord::Base
        include Clearance::App::Models::User
        has_many :wishes
      end




Saturday, March 14, 2009
app/models/wish.rb
      class Wish < ActiveRecord::Base
        belongs_to :user
      end



      app/models/user.rb
    When I make a “New car” wish
    Then “New <car” should appear on my wish
     class User   ActiveRecord::Base
       include Clearance::App::Models::User
       has_many the following element’s content to include
      expected:wishes
      “Your wish has been added!”
     end




Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
      it quot;should notify the user of creation via the flashquot; do
        do_post
        flash[:success].should == quot;Your wish has been added!quot;
      end




Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
      it quot;should notify the user of creation via the flashquot; do
        do_post
        flash[:success].should == quot;Your wish has been added!quot;
      end



      app/controllers/wishes_controller.rb
      def create
        current_user.wishes.create(params['wish'])
        flash[:success] = quot;Your wish has been added!quot;
        redirect_to :action => :index
      end




Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
      it quot;should notify the user of creation via the flashquot; do
        do_post
        flash[:success].should == quot;Your wish has been added!quot;
      end



     app/controllers/wishes_controller.rb
    Then “New car” should appear on my wish
      expected the following element’s content to include
     def create
      “New car”
       current_user.wishes.create(params['wish'])
        flash[:success] = quot;Your wish has been added!quot;
        redirect_to :action => :index
      end




Saturday, March 14, 2009
app/views/wishes/index.html.erb
      <ul>
      <% @wishes.each do |wish| %>
        <li><%= wish.name %></li>
      <% end %>
      </ul>




Saturday, March 14, 2009
spec/controllers/wishes_controller_spec.rb
           describe quot;GET / (#index)quot; do
             def do_get
               get :index
             end

                it quot;should assign the user's wishes to the viewquot; do
                  do_get
                  assigns[:wishes].should == @current_user.wishes
                end

           end




Saturday, March 14, 2009
app/controllers/wishes_controller.rb
           def index
             @wishes = current_user.wishes
           end




Saturday, March 14, 2009
Saturday, March 14, 2009
Saturday, March 14, 2009
Scenario: view members list
        Given the following wishes exist
          | Wish          | Family Member    |
          | Laptop        | Thomas           |
          | Nintendo Wii | Candace           |
          | CHEEZBURGER   | FuzzBuzz         |

            When I view the wish list for quot;Candacequot;

            Then I should see the following wishes
              | Wish          |
              | Nintendo Wii |




Saturday, March 14, 2009
Given the following   wishes exist
              | Wish          |   Family Member   |
              | Laptop        |   Thomas          |
              | Nintendo Wii |    Candace         |
              | CHEEZBURGER   |   FuzzBuzz        |

      features/step_definitions/wish_steps.rb
      Given /^the following wishes exist$/ do |table|




        end
      end




Saturday, March 14, 2009
Given the following   wishes exist
              | Wish          |   Family Member   |
              | Laptop        |   Thomas          |
              | Nintendo Wii |    Candace         |
              | CHEEZBURGER   |   FuzzBuzz        |

      features/step_definitions/wish_steps.rb
      Given /^the following wishes exist$/ do |table|
        table.hashes.each do |row|
          member = User.find_by_name(row[quot;Family Memberquot;]) ||
            create_user(:name => row[quot;Family Memberquot;])

          member.wishes.create!(:name => row[quot;Wishquot;])
        end
      end




Saturday, March 14, 2009
Feature: Addition
        In order to avoid silly mistakes
        As a math idiot
        I want to be told the sum of two numbers

           Scenario Outline: Add two numbers
             Given I have entered <input_1> into the calculator
             And I have entered <input_2> into the calculator
             When I press <button>
             Then the result should be <output> on the screen

           Examples:
             | input_1     |   input_2   |   button   |   output   |
             | 20          |   30        |   add      |   50       |
             |2            |   5         |   add      |   7        |
             |0            |   40        |   add      |   40       |




Saturday, March 14, 2009
Feature: Addition
        In order to avoid silly mistakes
        As a math idiot
        I want to be told the sum of two numbers

           Scenario Outline: Add two numbers
             Given I have entered <input_1> into the calculator
             And I have entered <input_2> into the calculator
             When I press <button>
             Then the result should be <output> on the screen

           Examples:
             | input_1     |   input_2   |   button   |   output   |
             | 20          |   30        |   add      |   50       |
             |2            |   5         |   add      |   7        |
             |0            |   40        |   add      |   40       |




Saturday, March 14, 2009
Feature: Addition
        In order to avoid silly mistakes
        As a math idiot
        I want to be told the sum of two numbers

           Scenario Outline: Add two numbers
             Given I have entered <input_1> into the calculator
             And I have entered <input_2> into the calculator
             When I press <button>
             Then the result should be <output> on the screen

           Examples:
             | input_1     |   input_2   |   button   |   output   |
             | 20          |   30        |   add      |   50       |
             |2            |   5         |   add      |   7        |
             |0            |   40        |   add      |   40       |




Saturday, March 14, 2009
Feature: Addition
        In order to avoid silly mistakes
        As a math idiot
        I want to be told the sum of two numbers

           Scenario Outline: Add two numbers
             Given I have entered <input_1> into the calculator
             And I have entered <input_2> into the calculator
             When I press <button>
             Then the result should be <output> on the screen

           Examples:
             | input_1     |   input_2   |   button   |   output   |
             | 20          |   30        |   add      |   50       |
             |2            |   5         |   add      |   7        |
             |0            |   40        |   add      |   40       |




Saturday, March 14, 2009
Feature: Addition
        In order to avoid silly mistakes
        As a math idiot
        I want to be told the sum of two numbers

           Scenario Outline: Add two numbers
             Given I have entered <input_1> into the calculator
             And I have entered <input_2> into the calculator
             When I press <button>
             Then the result should be <output> on the screen

           Examples:
             | input_1     |   input_2   |   button   |   output   |
             | 20          |   30        |   add      |   50       |
             |2            |   5         |   add      |   7        |
             |0            |   40        |   add      |   40       |




Saturday, March 14, 2009
Steps Within Steps
    When /^I view the wish list for quot;(.+)quot;$/ do |user_name|
      Given quot;I am logged inquot;
      visit quot;/wishes/#{user_name}quot;
    end




Saturday, March 14, 2009
Steps Within Steps
    When /^I view the wish list for quot;(.+)quot;$/ do |user_name|
      Given quot;I am logged inquot;
      visit quot;/wishes/#{user_name}quot;
    end




Saturday, March 14, 2009
Helpers - Ruby FTW
    When /^I view the wish list for quot;(.+)quot;$/ do |user_name|
      login_as(create_user)
      visit quot;/wishes/#{user_name}quot;
    end




Saturday, March 14, 2009
Helpers - Ruby FTW
    When /^I view the wish list for quot;(.+)quot;$/ do |user_name|
      login_as(create_user)
      visit quot;/wishes/#{user_name}quot;
    end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)

        visit new_session_path
        fill_in quot;Emailquot;, :with => @current_user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb

      def login_as(user)
        visit new_session_path
        fill_in quot;Emailquot;, :with => user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
        # make sure we have actually logged in- so we fail fast if not
        response.should contain(quot;Signed in successfullyquot;)
      end

      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)
        login_as(@current_user)
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb

      def login_as(user)
        visit new_session_path
        fill_in quot;Emailquot;, :with => user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
        click_button
        # make sure we have actually logged in- so we fail fast if not
        response.should contain(quot;Signed in successfullyquot;)
      end

      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)
        login_as(@current_user)
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb

      def login_as(user)
        visit new_session_path
        fill_in quot;Emailquot;, :with => user.email
        fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
                           “Every time you monkeypatch
        click_button
        # make sure we have actually logged in- so we fail fast if not
                               Object, a kitten dies.”
        response.should contain(quot;Signed in successfullyquot;)
      end

      Given /^I am logged in$/ do
        @current_user = create_user(:email_confirmed => true)
        login_as(@current_user)
      end




             http://seanohalpin.github.com/unobtrusive-metaprogramming/unobtrusive-metaprogramming.html


Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      module UserHelpers
        def login_as(user)
          visit new_session_path
          fill_in quot;Emailquot;, :with => user.email
          fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
          click_button
          # make sure we have actually logged in- so we fail fast if not
          response.should contain(quot;Signed in successfullyquot;)
        end
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      module UserHelpers
        def login_as(user)
          visit new_session_path
          fill_in quot;Emailquot;, :with => user.email
          fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
          click_button
          # make sure we have actually logged in- so we fail fast if not
          response.should contain(quot;Signed in successfullyquot;)
        end
      end




Saturday, March 14, 2009
features/step_definitions/user_steps.rb
      module UserHelpers
        def login_as(user)
          visit new_session_path
          fill_in quot;Emailquot;, :with => user.email
          fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;]
          click_button
          # make sure we have actually logged in- so we fail fast if not
          response.should contain(quot;Signed in successfullyquot;)
        end
      end

      World { |world| world.extend UserHelpers }




Saturday, March 14, 2009
I can skp teh
              unit testz?




Saturday, March 14, 2009
Acceptance Tests                Unit Tests

                Application Level               Object Level- Isolated!
                For Customers                   For developers
                Slow                            FAST! (should be at least)
                Good confidence                  - Tighter Feedback Loop
                Prevent against                 More about design!!!!!!!!!!!!
                regression




                    Will need both gears!
                    Some things are easier to
                    test at the application
                    level and vice-versa.




Saturday, March 14, 2009
SRSLY?
       Model specs,
       Controller Specs,
       and view specs!?


Saturday, March 14, 2009
Saturday, March 14, 2009
Saturday, March 14, 2009
Saturday, March 14, 2009
W
Saturday, March 14, 2009
M
Saturday, March 14, 2009
More tests ==
                  More Maintenance




Saturday, March 14, 2009
Test Value =
                          Design +
                      Documentation +
                          Defence
                           (regression)


Saturday, March 14, 2009
if test.value > test.cost
      Suite.add(test)
    end




Saturday, March 14, 2009
KTHXBYE!
                           BenMabey.com
               github.com/bmabey
                           Twitter: bmabey
                             IRC: mabes
Saturday, March 14, 2009

More Related Content

Similar to Outside-In Development With Ben Mabey

Encontro Locaweb Porto Alegre
Encontro  Locaweb Porto AlegreEncontro  Locaweb Porto Alegre
Encontro Locaweb Porto AlegreFabio Akita
 
Termtter 2009-03-14
Termtter 2009-03-14Termtter 2009-03-14
Termtter 2009-03-14jugyo kohno
 
WordPress SEO - SEO-Campixx
WordPress SEO - SEO-CampixxWordPress SEO - SEO-Campixx
WordPress SEO - SEO-Campixxsteffenhd
 
Chad Udell - Developers are from Mars, Designers are from Venus
Chad Udell - Developers are from Mars, Designers are from VenusChad Udell - Developers are from Mars, Designers are from Venus
Chad Udell - Developers are from Mars, Designers are from Venus360|Conferences
 
Treetop - I'd rather have one problem
Treetop - I'd rather have one problemTreetop - I'd rather have one problem
Treetop - I'd rather have one problemRoland Swingler
 
Avoiding common Accessibility mistakes
Avoiding common Accessibility mistakesAvoiding common Accessibility mistakes
Avoiding common Accessibility mistakesDirk Ginader
 
Background Processing in Ruby on Rails
Background Processing in Ruby on RailsBackground Processing in Ruby on Rails
Background Processing in Ruby on Railsrobmack
 
FFI - building cross engine ruby extensions
FFI - building cross engine ruby extensionsFFI - building cross engine ruby extensions
FFI - building cross engine ruby extensionsJeremy Hinegardner
 
Beyond The Web: Drupal Meets The Desktop (And Mobile)
Beyond The Web: Drupal Meets The Desktop (And Mobile)Beyond The Web: Drupal Meets The Desktop (And Mobile)
Beyond The Web: Drupal Meets The Desktop (And Mobile)Justin Miller
 
Google Summer of Code™ (in English; neutral version)
Google Summer of Code™ (in English; neutral version)Google Summer of Code™ (in English; neutral version)
Google Summer of Code™ (in English; neutral version)Dirk Haun
 
Intuitdanbartowawstour2009 090617135031 Phpapp02
Intuitdanbartowawstour2009 090617135031 Phpapp02Intuitdanbartowawstour2009 090617135031 Phpapp02
Intuitdanbartowawstour2009 090617135031 Phpapp02GovCloud Network
 
AWS Customer Presentation - Intuit
AWS Customer Presentation - IntuitAWS Customer Presentation - Intuit
AWS Customer Presentation - IntuitAmazon Web Services
 
Rack Middleware
Rack MiddlewareRack Middleware
Rack MiddlewareJon Crosby
 
Oxente on Rails 2009
Oxente on Rails 2009Oxente on Rails 2009
Oxente on Rails 2009Fabio Akita
 
OpenID DrupalCon 2009
OpenID DrupalCon 2009OpenID DrupalCon 2009
OpenID DrupalCon 2009James Walker
 
Presentation by David Troy at eComm 2009, San Francisco, CA
Presentation by David Troy at eComm 2009, San Francisco, CAPresentation by David Troy at eComm 2009, San Francisco, CA
Presentation by David Troy at eComm 2009, San Francisco, CADavid Troy
 

Similar to Outside-In Development With Ben Mabey (20)

Encontro Locaweb Porto Alegre
Encontro  Locaweb Porto AlegreEncontro  Locaweb Porto Alegre
Encontro Locaweb Porto Alegre
 
Termtter 2009-03-14
Termtter 2009-03-14Termtter 2009-03-14
Termtter 2009-03-14
 
Dev In Rio 2009
Dev In Rio 2009Dev In Rio 2009
Dev In Rio 2009
 
WordPress SEO - SEO-Campixx
WordPress SEO - SEO-CampixxWordPress SEO - SEO-Campixx
WordPress SEO - SEO-Campixx
 
Chad Udell - Developers are from Mars, Designers are from Venus
Chad Udell - Developers are from Mars, Designers are from VenusChad Udell - Developers are from Mars, Designers are from Venus
Chad Udell - Developers are from Mars, Designers are from Venus
 
Treetop - I'd rather have one problem
Treetop - I'd rather have one problemTreetop - I'd rather have one problem
Treetop - I'd rather have one problem
 
Avoiding common Accessibility mistakes
Avoiding common Accessibility mistakesAvoiding common Accessibility mistakes
Avoiding common Accessibility mistakes
 
Background Processing in Ruby on Rails
Background Processing in Ruby on RailsBackground Processing in Ruby on Rails
Background Processing in Ruby on Rails
 
FFI - building cross engine ruby extensions
FFI - building cross engine ruby extensionsFFI - building cross engine ruby extensions
FFI - building cross engine ruby extensions
 
Beyond The Web: Drupal Meets The Desktop (And Mobile)
Beyond The Web: Drupal Meets The Desktop (And Mobile)Beyond The Web: Drupal Meets The Desktop (And Mobile)
Beyond The Web: Drupal Meets The Desktop (And Mobile)
 
Locos x Rails
Locos x RailsLocos x Rails
Locos x Rails
 
Google Summer of Code™ (in English; neutral version)
Google Summer of Code™ (in English; neutral version)Google Summer of Code™ (in English; neutral version)
Google Summer of Code™ (in English; neutral version)
 
Intuitdanbartowawstour2009 090617135031 Phpapp02
Intuitdanbartowawstour2009 090617135031 Phpapp02Intuitdanbartowawstour2009 090617135031 Phpapp02
Intuitdanbartowawstour2009 090617135031 Phpapp02
 
AWS Customer Presentation - Intuit
AWS Customer Presentation - IntuitAWS Customer Presentation - Intuit
AWS Customer Presentation - Intuit
 
Rack Middleware
Rack MiddlewareRack Middleware
Rack Middleware
 
Oxente on Rails 2009
Oxente on Rails 2009Oxente on Rails 2009
Oxente on Rails 2009
 
Making a living with WordPress in 2009
Making a living with WordPress in 2009Making a living with WordPress in 2009
Making a living with WordPress in 2009
 
Depot Best Practices
Depot Best PracticesDepot Best Practices
Depot Best Practices
 
OpenID DrupalCon 2009
OpenID DrupalCon 2009OpenID DrupalCon 2009
OpenID DrupalCon 2009
 
Presentation by David Troy at eComm 2009, San Francisco, CA
Presentation by David Troy at eComm 2009, San Francisco, CAPresentation by David Troy at eComm 2009, San Francisco, CA
Presentation by David Troy at eComm 2009, San Francisco, CA
 

More from Ben Mabey

PCA for the uninitiated
PCA for the uninitiatedPCA for the uninitiated
PCA for the uninitiatedBen Mabey
 
Clojure, Plain and Simple
Clojure, Plain and SimpleClojure, Plain and Simple
Clojure, Plain and SimpleBen Mabey
 
Cucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already SpeakCucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already SpeakBen Mabey
 
Writing Software not Code with Cucumber
Writing Software not Code with CucumberWriting Software not Code with Cucumber
Writing Software not Code with CucumberBen Mabey
 
Disconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecordDisconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecordBen Mabey
 
SVD and the Netflix Dataset
SVD and the Netflix DatasetSVD and the Netflix Dataset
SVD and the Netflix DatasetBen Mabey
 

More from Ben Mabey (7)

PCA for the uninitiated
PCA for the uninitiatedPCA for the uninitiated
PCA for the uninitiated
 
Clojure, Plain and Simple
Clojure, Plain and SimpleClojure, Plain and Simple
Clojure, Plain and Simple
 
Github flow
Github flowGithub flow
Github flow
 
Cucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already SpeakCucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already Speak
 
Writing Software not Code with Cucumber
Writing Software not Code with CucumberWriting Software not Code with Cucumber
Writing Software not Code with Cucumber
 
Disconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecordDisconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecord
 
SVD and the Netflix Dataset
SVD and the Netflix DatasetSVD and the Netflix Dataset
SVD and the Netflix Dataset
 

Recently uploaded

Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxLoriGlavin3
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
What is Artificial Intelligence?????????
What is Artificial Intelligence?????????What is Artificial Intelligence?????????
What is Artificial Intelligence?????????blackmambaettijean
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate AgentsRyan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate AgentsRyan Mahoney
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxLoriGlavin3
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfMounikaPolabathina
 

Recently uploaded (20)

Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptxUse of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
Use of FIDO in the Payments and Identity Landscape: FIDO Paris Seminar.pptx
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
What is Artificial Intelligence?????????
What is Artificial Intelligence?????????What is Artificial Intelligence?????????
What is Artificial Intelligence?????????
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate AgentsRyan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
Ryan Mahoney - Will Artificial Intelligence Replace Real Estate Agents
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptxThe Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
The Fit for Passkeys for Employee and Consumer Sign-ins: FIDO Paris Seminar.pptx
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdf
 

Outside-In Development With Ben Mabey

  • 1. Outside-In Development With Ben Mabey Saturday, March 14, 2009
  • 2. Who’s my audience? Saturday, March 14, 2009
  • 4. Scenario: planning meeting Given I am a Software Developer When I am given a feature request Then I should... Saturday, March 14, 2009
  • 5. What is the most CRITICAL word to keep in mind? Saturday, March 14, 2009
  • 7. Scenario: planning meeting Given I am a Software Developer When I am given a feature request Then I should ask why. Saturday, March 14, 2009
  • 10. Feature Request: Print Reports Story from Thought works: http:// www.theregister.co.uk/ 2007 /06/25/ Saturday, March 14, 2009
  • 12. Protect Revenue Increase Revenue Manage Cost Saturday, March 14, 2009
  • 13. * not executed * documentation value Feature: title * variant of contextra * business value up front In order to [Business Value] As a [Role] I want to [Some Action] (feature) Saturday, March 14, 2009
  • 14. There is no template. What is important to have in narrative: * business value * stakeholder role * user role * action to be taken by user Saturday, March 14, 2009
  • 15. Scenario: title Given [Context] When I do [Action] Then I should see [Outcome] Saturday, March 14, 2009
  • 16. Scenario: title Given [Context] And [More Context] When I do [Action] And [Other Action] Then I should see [Outcome] But I should not see [Outcome] Saturday, March 14, 2009
  • 17. project_root/ | `-- features Saturday, March 14, 2009
  • 18. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature Saturday, March 14, 2009
  • 19. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb Saturday, March 14, 2009
  • 20. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb |-- step_definitions | |-- domain_concept_A.rb | `-- domain_concept_B.rb Saturday, March 14, 2009
  • 21. Step Given a widget Saturday, March 14, 2009
  • 22. Step Definition Given /^a widget$/ do Given a widget #codes go here end Saturday, March 14, 2009
  • 23. Step Definition Step Mother Given /^a widget$/ do Given a widget #codes go here end Saturday, March 14, 2009
  • 24. Step Definition Step Mother Given /^a widget$/ do Given a widget #codes go here end Saturday, March 14, 2009
  • 29. Steps are pending Saturday, March 14, 2009
  • 31. Go Down A Gear Saturday, March 14, 2009
  • 33. RSpec We ain’t got no RSPEC! Saturday, March 14, 2009
  • 35. Write Code Example (Unit Test) Saturday, March 14, 2009
  • 36. Make Example Pass Saturday, March 14, 2009
  • 38. Where Are we? Saturday, March 14, 2009
  • 40. REFACTOR and REPEAT Saturday, March 14, 2009
  • 41. features/manage_my_wishes.feature Feature: manage my wishes In order to get more stuff As a greedy person I want to manage my wish list for my family members to view Scenario: add wish Given I am logged in When I make a quot;New carquot; wish Then quot;New carquot; should appear on my wish list Saturday, March 14, 2009
  • 43. Line # of scenario Saturday, March 14, 2009
  • 45. I can has backtraze? Given I am logged in #features/manage_my_wishes.feature:8 Saturday, March 14, 2009
  • 47. features/i_can_has_wishez_plz.feature OH HAI: I CAN HAS WISHEZ PLZ SO DAT I CAN HAS HUGS TIEM AN PLAY TIEM AN SLEEP TIEM AN NOM TIEM AS NICE GREEDEE KITTEH I CAN PLZ MANEGE TEH WISHEZ MISHUN: MAK NEW WISH GIVN I AM LOGGD IN WEN I MAK quot;CHEEZBURGERquot; WISH DEN I C quot;CHEEZBURGERquot; ON ME WISHEZ LIT Saturday, March 14, 2009
  • 50. cucumber some.feature --language en-lol Saturday, March 14, 2009
  • 51. cucumber some.feature --language en-lol Saturday, March 14, 2009
  • 54. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end Saturday, March 14, 2009
  • 55. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end Test Data Builder / Object Mother Saturday, March 14, 2009
  • 56. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end Fixture Replacement, Fixjour, Factory Girl, etc spec/fixjour_builders.rb Fixjour do define_builder(User) do |klass, overrides| klass.new( :email => quot;user#{counter(:user)}@email.comquot;, :password => 'password', :password_confirmation => 'password' ) end end Saturday, March 14, 2009
  • 57. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end Saturday, March 14, 2009
  • 58. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end Saturday, March 14, 2009
  • 59. features/step_definitions/user_steps.rb Webrat / Awesomeness Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end Saturday, March 14, 2009
  • 60. features/step_definitions/user_steps.rb Webrat / Awesomeness Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end Saturday, March 14, 2009
  • 61. features/step_definitions/user_steps.rb Webrat / Awesomeness Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end features/support/env.rb require 'webrat' Webrat.configure do |config| config.mode = :rails end Saturday, March 14, 2009
  • 62. features/step_definitions/user_steps.rb Webrat / Awesomeness Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end features/support/env.rb require 'webrat' Webrat.configure do |config| config.mode = :rails end Adapter Saturday, March 14, 2009
  • 63. features/step_definitions/user_steps.rb Webrat / Awesomeness Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end features/step_definitions/webrat_steps.rb When /^I press quot;(.*)quot;$/ do |button| click_button(button) end 19 Steps Out-of-box When /^I follow quot;(.*)quot;$/ do |link| click_link(link) end When /^I fill in quot;(.*)quot; with quot;(.*)quot;$/ do |field, value| fill_in(field, :with => value) end Saturday, March 14, 2009
  • 64. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end Saturday, March 14, 2009
  • 65. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not session[:user_id].should == @current_user.id end Saturday, March 14, 2009
  • 66. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not session[:user_id].should == @current_user.id controller.current_user.should == @current_user end Saturday, March 14, 2009
  • 67. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path Specify outcome, not implementation. fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not session[:user_id].should == @current_user.id controller.current_user.should == @current_user response.should contain(quot;Signed in successfullyquot;) end Saturday, March 14, 2009
  • 68. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not response.should contain(quot;Signed in successfullyquot;) end Saturday, March 14, 2009
  • 70. No route matches “/sessions/create” with {:method=>:post} (ActionController::RoutingError) Saturday, March 14, 2009
  • 71. I’m going to cheat... Saturday, March 14, 2009
  • 72. I’m going to cheat... $ gem install thoughtbot-clearance $ ./script generate clearance $ ./script generate clearance_features Saturday, March 14, 2009
  • 74. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| end Then /^(.+) should appear on my wish list$/ do |wish| end Saturday, March 14, 2009
  • 75. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| end Then /^(.+) should appear on my wish list$/ do |wish| end Regexp Capture -> Yielded Variable Saturday, March 14, 2009
  • 76. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| visit quot;/wishesquot; click_link quot;Make a wishquot; fill_in quot;Wishquot;, :with => wish click_button end Then /^(.+) should appear on my wish list$/ do |wish| end Saturday, March 14, 2009
  • 77. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| visit quot;/wishesquot; click_link quot;Make a wishquot; fill_in quot;Wishquot;, :with => wish click_button end Then /^(.+) should appear on my wish list$/ do |wish| response.should contain(quot;Your wish has been added!quot;) response.should contain(wish) end Saturday, March 14, 2009
  • 78. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| visit quot;/wishesquot; click_link quot;Make a wishquot; fill_in quot;Wishquot;, :with => wish click_button end No route matches “/wishes” with {:method=>:get} appear on my wish list$/ do |wish| Then /^(.+) should (ActionController::RoutingError) response.should contain(quot;Your wish has been added!quot;) response.should contain(wish) end Saturday, March 14, 2009
  • 79. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes Saturday, March 14, 2009
  • 80. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes When I make a “New car” wish uninitialized constant WishesController (NameError) Saturday, March 14, 2009
  • 81. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes $./script generate rspec_controller new create Saturday, March 14, 2009
  • 82. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes When I make a “New car” wish Could not find link with text or title or id “Make a wish” (Webrat::NotFoundError) Saturday, March 14, 2009
  • 83. app/views/wishes/index.html.erb <%= link_to quot;Make a wishquot;, new_wish_path %> Saturday, March 14, 2009
  • 84. app/views/wishes/index.html.erb <%= link_to quot;Make a wishquot;, new_wish_path %> When I make a “New car” wish Could not find field: “Wish” (Webrat::NotFoundError) Saturday, March 14, 2009
  • 85. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| visit quot;/wishesquot; click_link quot;Make a wishquot; fill_in quot;Wishquot;, :with => wish click_button end Saturday, March 14, 2009
  • 86. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| visit quot;/wishesquot; click_link quot;Make a wishquot; fill_in quot;Wishquot;, :with => wish click_button end app/views/wishes/new.html.erb <% form_for :wish do |f| %> <%= f.label :name, quot;Wishquot; %> <%= f.text_field :name %> <%= submit_tag quot;Make the wish!quot; %> <% end %> Saturday, March 14, 2009
  • 87. features/step_definitions/wish_steps.rb When /^I make a quot;(.+)quot; wish$/ do |wish| visit quot;/wishesquot; click_link quot;Make a wishquot; fill_in quot;Wishquot;, :with => wish click_button end Location Strategy FTW! app/views/wishes/new.html.erb <% form_for :wish do |f| %> <%= f.label :name, quot;Wishquot; %> <%= f.text_field :name %> <%= submit_tag quot;Make the wish!quot; %> <% end %> Saturday, March 14, 2009
  • 88. View Controller Saturday, March 14, 2009
  • 89. spec/controllers/wishes_controller_spec.rb describe WishesController do describe quot;POST / (#create)quot; do end end Saturday, March 14, 2009
  • 90. spec/controllers/wishes_controller_spec.rb describe WishesController do describe quot;POST / (#create)quot; do it quot;should create a new wish for the user with the paramsquot; do user = mock_model(User, :wishes => mock(quot;wishes associationquot;)) controller.stub!(:current_user).and_return(user) user.wishes.should_receive(:create).with(wish_params) post :create, 'wish' => {'name' => 'Dog'} end end end Saturday, March 14, 2009
  • 91. app/controllers/wishes_controller.rb class WishesController < ApplicationController def create current_user.wishes.create(params['wish']) end end Saturday, March 14, 2009
  • 92. spec/controllers/wishes_controller_spec.rb describe WishesController do describe quot;POST / (#create)quot; do before(:each) do ..... it quot;should redirect the user to their wish listquot; do do_post response.should redirect_to(wishes_path) end end end Saturday, March 14, 2009
  • 93. app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) redirect_to :action => :index end Saturday, March 14, 2009
  • 94. View Controller Model Saturday, March 14, 2009
  • 95. app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) redirect_to :action => :index end When I make a “New car” wish undefined method `wishes` for #<User:0x268e898> (NoMethodError) Saturday, March 14, 2009
  • 96. app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) redirect_to :action => :index end $./script generate rspec_model wish name:string user_id:integer Saturday, March 14, 2009
  • 97. app/models/wish.rb class Wish < ActiveRecord::Base belongs_to :user end app/models/user.rb class User < ActiveRecord::Base include Clearance::App::Models::User has_many :wishes end Saturday, March 14, 2009
  • 98. app/models/wish.rb class Wish < ActiveRecord::Base belongs_to :user end app/models/user.rb When I make a “New car” wish Then “New <car” should appear on my wish class User ActiveRecord::Base include Clearance::App::Models::User has_many the following element’s content to include expected:wishes “Your wish has been added!” end Saturday, March 14, 2009
  • 99. spec/controllers/wishes_controller_spec.rb it quot;should notify the user of creation via the flashquot; do do_post flash[:success].should == quot;Your wish has been added!quot; end Saturday, March 14, 2009
  • 100. spec/controllers/wishes_controller_spec.rb it quot;should notify the user of creation via the flashquot; do do_post flash[:success].should == quot;Your wish has been added!quot; end app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) flash[:success] = quot;Your wish has been added!quot; redirect_to :action => :index end Saturday, March 14, 2009
  • 101. spec/controllers/wishes_controller_spec.rb it quot;should notify the user of creation via the flashquot; do do_post flash[:success].should == quot;Your wish has been added!quot; end app/controllers/wishes_controller.rb Then “New car” should appear on my wish expected the following element’s content to include def create “New car” current_user.wishes.create(params['wish']) flash[:success] = quot;Your wish has been added!quot; redirect_to :action => :index end Saturday, March 14, 2009
  • 102. app/views/wishes/index.html.erb <ul> <% @wishes.each do |wish| %> <li><%= wish.name %></li> <% end %> </ul> Saturday, March 14, 2009
  • 103. spec/controllers/wishes_controller_spec.rb describe quot;GET / (#index)quot; do def do_get get :index end it quot;should assign the user's wishes to the viewquot; do do_get assigns[:wishes].should == @current_user.wishes end end Saturday, March 14, 2009
  • 104. app/controllers/wishes_controller.rb def index @wishes = current_user.wishes end Saturday, March 14, 2009
  • 107. Scenario: view members list Given the following wishes exist | Wish | Family Member | | Laptop | Thomas | | Nintendo Wii | Candace | | CHEEZBURGER | FuzzBuzz | When I view the wish list for quot;Candacequot; Then I should see the following wishes | Wish | | Nintendo Wii | Saturday, March 14, 2009
  • 108. Given the following wishes exist | Wish | Family Member | | Laptop | Thomas | | Nintendo Wii | Candace | | CHEEZBURGER | FuzzBuzz | features/step_definitions/wish_steps.rb Given /^the following wishes exist$/ do |table| end end Saturday, March 14, 2009
  • 109. Given the following wishes exist | Wish | Family Member | | Laptop | Thomas | | Nintendo Wii | Candace | | CHEEZBURGER | FuzzBuzz | features/step_definitions/wish_steps.rb Given /^the following wishes exist$/ do |table| table.hashes.each do |row| member = User.find_by_name(row[quot;Family Memberquot;]) || create_user(:name => row[quot;Family Memberquot;]) member.wishes.create!(:name => row[quot;Wishquot;]) end end Saturday, March 14, 2009
  • 110. Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen Examples: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | |2 | 5 | add | 7 | |0 | 40 | add | 40 | Saturday, March 14, 2009
  • 111. Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen Examples: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | |2 | 5 | add | 7 | |0 | 40 | add | 40 | Saturday, March 14, 2009
  • 112. Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen Examples: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | |2 | 5 | add | 7 | |0 | 40 | add | 40 | Saturday, March 14, 2009
  • 113. Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen Examples: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | |2 | 5 | add | 7 | |0 | 40 | add | 40 | Saturday, March 14, 2009
  • 114. Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen Examples: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | |2 | 5 | add | 7 | |0 | 40 | add | 40 | Saturday, March 14, 2009
  • 115. Steps Within Steps When /^I view the wish list for quot;(.+)quot;$/ do |user_name| Given quot;I am logged inquot; visit quot;/wishes/#{user_name}quot; end Saturday, March 14, 2009
  • 116. Steps Within Steps When /^I view the wish list for quot;(.+)quot;$/ do |user_name| Given quot;I am logged inquot; visit quot;/wishes/#{user_name}quot; end Saturday, March 14, 2009
  • 117. Helpers - Ruby FTW When /^I view the wish list for quot;(.+)quot;$/ do |user_name| login_as(create_user) visit quot;/wishes/#{user_name}quot; end Saturday, March 14, 2009
  • 118. Helpers - Ruby FTW When /^I view the wish list for quot;(.+)quot;$/ do |user_name| login_as(create_user) visit quot;/wishes/#{user_name}quot; end Saturday, March 14, 2009
  • 119. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path fill_in quot;Emailquot;, :with => @current_user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button end Saturday, March 14, 2009
  • 120. features/step_definitions/user_steps.rb def login_as(user) visit new_session_path fill_in quot;Emailquot;, :with => user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not response.should contain(quot;Signed in successfullyquot;) end Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) login_as(@current_user) end Saturday, March 14, 2009
  • 121. features/step_definitions/user_steps.rb def login_as(user) visit new_session_path fill_in quot;Emailquot;, :with => user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not response.should contain(quot;Signed in successfullyquot;) end Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) login_as(@current_user) end Saturday, March 14, 2009
  • 122. features/step_definitions/user_steps.rb def login_as(user) visit new_session_path fill_in quot;Emailquot;, :with => user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] “Every time you monkeypatch click_button # make sure we have actually logged in- so we fail fast if not Object, a kitten dies.” response.should contain(quot;Signed in successfullyquot;) end Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) login_as(@current_user) end http://seanohalpin.github.com/unobtrusive-metaprogramming/unobtrusive-metaprogramming.html Saturday, March 14, 2009
  • 123. features/step_definitions/user_steps.rb module UserHelpers def login_as(user) visit new_session_path fill_in quot;Emailquot;, :with => user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not response.should contain(quot;Signed in successfullyquot;) end end Saturday, March 14, 2009
  • 124. features/step_definitions/user_steps.rb module UserHelpers def login_as(user) visit new_session_path fill_in quot;Emailquot;, :with => user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not response.should contain(quot;Signed in successfullyquot;) end end Saturday, March 14, 2009
  • 125. features/step_definitions/user_steps.rb module UserHelpers def login_as(user) visit new_session_path fill_in quot;Emailquot;, :with => user.email fill_in quot;Passwordquot;, :with => valid_user_attributes[quot;passwordquot;] click_button # make sure we have actually logged in- so we fail fast if not response.should contain(quot;Signed in successfullyquot;) end end World { |world| world.extend UserHelpers } Saturday, March 14, 2009
  • 126. I can skp teh unit testz? Saturday, March 14, 2009
  • 127. Acceptance Tests Unit Tests Application Level Object Level- Isolated! For Customers For developers Slow FAST! (should be at least) Good confidence - Tighter Feedback Loop Prevent against More about design!!!!!!!!!!!! regression Will need both gears! Some things are easier to test at the application level and vice-versa. Saturday, March 14, 2009
  • 128. SRSLY? Model specs, Controller Specs, and view specs!? Saturday, March 14, 2009
  • 134. More tests == More Maintenance Saturday, March 14, 2009
  • 135. Test Value = Design + Documentation + Defence (regression) Saturday, March 14, 2009
  • 136. if test.value > test.cost Suite.add(test) end Saturday, March 14, 2009
  • 137. KTHXBYE! BenMabey.com github.com/bmabey Twitter: bmabey IRC: mabes Saturday, March 14, 2009

Editor's Notes