Archive for the ‘Chores’ Category

Chores: A test driven website – Part 10

Monday, June 8th, 2009

Where was I?

By the time I finished up post nine and started working on post 10, I was completely lost. It was entirely my fault and it happens to the best of us, and me too. Coming back to the Chores app after time had passed, and looking around, I thought two things:

  • How did this get to be such a mess?
  • Where was I?

A number of factors had come into play that led to this point. The first of which was lack of discipline. As came across features I thought were interesting or necessary, I abandoned the test driven mantra of red, green, refactor. This ruined my focus, splintered my development efforts and generated a fair share of untested code.

Another issue I ran into is that over the time between my first posts and now, a number of new versions of libraries were released and Rails itself moved from version 2.2.2 to 2.3.2. Subtle variations in the APIs for these gems, generated a few errors and left me scratching my head more than once. I would like to say that change is good, it’s great that the Ruby community is so active in improving it’s products. However, I needed to be more proactive in managing the changes.

Time is another factor that worked it’s entropy in Chores. You hear people talking about the poor developer left to maintain sloppy code months or years after it’s release, and that they may be you. Well it was me, and I was feeling the hurt.

Not all is lost

Thus far, I’ve painted a pretty dire picture of the state of the code. I’ve probably left you thinking, “but this is Chores: A test driven website! How could this happen?” Well it does happen, but you are right. On the bright side, it is a test driven website. If it wasn’t I would probably be tempted to toss it out and start over. But, because it’s test driven and in source control, I’m fairly confident that I can get the codebase up to date and functional in a reasonable amount of time. This in addition to a few practices will have me back in business in no time

While these are good practices to follow in the future, what can I do to get this codebase back under control?

  • Git back to known state – Since I’ve done some unknown piecemeal development on chores I will use my post_9 tag.
  • Make sure existing tests pass – This is where our tests are worth their weight. We know what code is working, and what that code does.
  • Evaluate holes – Find the weak points in the tests and make a plan to fix them.

Git Happy

Note: You can find post_9 here, though if you happen to be following along, you would want to merge it into your own source.

My first step is to rewind to the tag I made for my last post. Right away I realise that I now have two source repositories, one on my shared host and one on github. One is really enough so part of the process will be to move to only github.

rubyyot@atlas:~/working/chores$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 4 commits.
#
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores$ git remote
github
origin
rubyyot@atlas:~/working/chores$ git remote rm origin
rubyyot@atlas:~/working/chores$ git remote
github
rubyyot@atlas:~/working/chores$ git remote add origin   git://github.com/rubyyot/chores.git

Above, I listed my remote repositories. I see that I have two, so I remove one and add another. A mistake I made here is that I set my remote to my public clone url. I should have, since I will be pushing to it, used my clone url, git@github.com:rubyyot/chores.git.

rubyyot@atlas:~/working/chores$ git status
rubyyot@atlas:~/working/chores$ git reset --hard post_9
HEAD is now at b0eecae added auto formatting to Identity.find_or_create_if_valid

Here I have done a hard (destructive) reset back to the tag post_9. I could have avoided doing such a dangerous procedure if I had been working on a branch.

rubyyot@atlas:~/working/chores$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       db/production.sqlite3
nothing added to commit but untracked files present (use "git add" to track)
rubyyot@atlas:~/working/chores$ vi .gitignore      #added db/*.sqlite3 to .gitignore
rubyyot@atlas:~/working/chores$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   .gitignore
#
no changes added to commit (use "git add" and/or "git commit -a")

Here I noticed that git sees my “production” sqlite3 database is not in sync with the index. I really don’t want or need to have a pseudo-production sqlite3 database in source control so I updated my .gitignore file to ignore it.

rubyyot@atlas:~/working/chores$ rake test
(in /home/rubyyot/working/chores)
/usr/bin/ruby1.8 -I"/home/rubyyot/working/chores/lib" -I"/home/rubyyot/working/chores/test" "/home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader.rb" "test/unit/identity_test.rb" "test/unit/chore_test.rb" "test/unit/child_test.rb"
Loaded suite /home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
Started
.............................
Finished in 0.347031 seconds.
 
29 tests, 43 assertions, 0 failures, 0 errors
/usr/bin/ruby1.8 -I"/home/rubyyot/working/chores/lib" -I"/home/rubyyot/working/chores/test" "/home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader.rb" "test/functional/children_controller_test.rb" "test/functional/chores_controller_test.rb" "test/functional/home_controller_test.rb"
Loaded suite /home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
Started
..........
Finished in 0.201701 seconds.
 
10 tests, 17 assertions, 0 failures, 0 errors
/usr/bin/ruby1.8 -I"/home/rubyyot/working/chores/lib" -I"/home/rubyyot/working/chores/test" "/home/rubyyot/.gem/ruby/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader.rb"
rubyyot@atlas:~/working/chores$ rake features
(in /home/rubyyot/working/chores)
/usr/bin/ruby1.8 -I "/usr/lib/ruby/gems/1.8/gems/cucumber-0.3.11/lib:lib" "/usr/lib/ruby/gems/1.8/gems/cucumber-0.3.11/bin/cucumber" --format pretty features/define_children.feature features/define_chores.feature
Story:  Define child
As a parent
I want to define my child(ren)
So that I can assign them chores
 
  Scenario: Defining a child                                        # features/define_children.feature:6
    Given I am logged in as "test.example.com"                      # features/step_definitions/chores_steps.rb:9
    And I am on the homepage                                        # features/step_definitions/webrat_steps.rb:6
    When I follow "Add Child"                                       # features/step_definitions/webrat_steps.rb:18
    And I fill in "child[nickname]" with "Bobby"                    # features/step_definitions/webrat_steps.rb:22
    And I fill in "child[open_identifier]" with "bobby.example.com" # features/step_definitions/webrat_steps.rb:22
    And I press "Add"                                               # features/step_definitions/webrat_steps.rb:14
    Then I should see "Child added."                                # features/step_definitions/webrat_steps.rb:93
    And I should see "Bobby"                                        # features/step_definitions/webrat_steps.rb:93
 
  Scenario: Defining a second child                                 # features/define_children.feature:16
    Given I am logged in as "test.example.com"                      # features/step_definitions/chores_steps.rb:9
    And I am on the homepage                                        # features/step_definitions/webrat_steps.rb:6
    When I follow "Add Child"                                       # features/step_definitions/webrat_steps.rb:18
    And I fill in "child[nickname]" with "Bobby"                    # features/step_definitions/webrat_steps.rb:22
    And I fill in "child[open_identifier]" with "bobby.example.com" # features/step_definitions/webrat_steps.rb:22
    And I press "Add"                                               # features/step_definitions/webrat_steps.rb:14
    And I follow "Add another Child"                                # features/step_definitions/webrat_steps.rb:18
    And I fill in "child[nickname]" with "Suzy"                     # features/step_definitions/webrat_steps.rb:22
    And I fill in "child[open_identifier]" with "suzy.example.com"  # features/step_definitions/webrat_steps.rb:22
    And I press "Add"                                               # features/step_definitions/webrat_steps.rb:14
    Then I should see "Child added."                                # features/step_definitions/webrat_steps.rb:93
    And I should see "Bobby"                                        # features/step_definitions/webrat_steps.rb:93
    And I should see "Suzy"                                         # features/step_definitions/webrat_steps.rb:93
 
Story:  Define chores
As a parent
I want to define chores
So that I can assign them to my children
 
  Scenario: Creating a chore                      # features/define_chores.feature:6
    Given I am logged in as "test.example.com"    # features/step_definitions/chores_steps.rb:9
    And I am on the homepage                      # features/step_definitions/webrat_steps.rb:6
    When I follow "Add Chore"                     # features/step_definitions/webrat_steps.rb:18
    And I fill in the chore with "My first chore" # features/step_definitions/chores_steps.rb:5
    And I press "Add"                             # features/step_definitions/webrat_steps.rb:14
    Then I should see "Chore added."              # features/step_definitions/webrat_steps.rb:93
    And I should see "My first chore"             # features/step_definitions/webrat_steps.rb:93
 
3 scenarios (3 passed)
28 steps (28 passed)
0m0.266s

Now I’ve run my tests and features and am pleasantly surprised to see that they are all passing. I didn’t expect this, but it’s a pleasant surprise. Still there could be some hidden surprises lurking in the shadows.

rubyyot@atlas:~/working/chores$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   .gitignore
#       modified:   db/test.sqlite3
#
no changes added to commit (use "git add" and/or "git commit -a")
rubyyot@atlas:~/working/chores$ git rm -f db/test.sqlite3
rm 'db/test.sqlite3'
rubyyot@atlas:~/working/chores$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       deleted:    db/test.sqlite3
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   .gitignore
#
rubyyot@atlas:~/working/chores$ git add .gitignore
rubyyot@atlas:~/working/chores$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   .gitignore
#       deleted:    db/test.sqlite3
#

After running my tests my sqlite3 test db shows as modified. Since my modification to .gitignore should be excluding all sqlite3 databases, it must already be in the git index. So I removed it and added the updated .gitignore to the index. I’m all ready for a commit.

rubyyot@atlas:~/working/chores$ git commit -m "resetting to post_9"
Created commit 79d6f46: resetting to post_9
 2 files changed, 1 insertions(+), 0 deletions(-)
 delete mode 100644 db/test.sqlite3
rubyyot@atlas:~/working/chores$ git push github
git push github
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa':
To git@github.com:rubyyot/chores.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'git@github.com:rubyyot/chores.git'

I make my commit and try to push to github. The attempt is rejected. Looks like I need to merge into it.

rubyyot@atlas:~/working/chores$ git pull github master
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa':
From git@github.com:rubyyot/chores
 * branch            master     -> FETCH_HEAD
Merge made by recursive.
 README |    6 ++++++
 1 files changed, 6 insertions(+), 0 deletions(-)
rubyyot@atlas:~/working/chores$ git merge ac60baa287c7ee73851d1b9895c7e6c826abddac
Already up-to-date.

I pull from github which appears to auto-merge. My manual attempt to merge tells me that I’m up to date.

rubyyot@atlas:~/working/chores$ vi README   #made a small change to README
rubyyot@atlas:~/working/chores$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 3 commits.
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   README
#
no changes added to commit (use "git add" and/or "git commit -a")
rubyyot@atlas:~/working/chores$ git add .
rubyyot@atlas:~/working/chores$ git commit -m "small change to try commit to github"
Created commit 9991b45: small change to try commit to github
 1 files changed, 1 insertions(+), 1 deletions(-)
rubyyot@atlas:~/working/chores$ git push github
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa':
Counting objects: 15, done.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 955 bytes, done.
Total 9 (delta 5), reused 0 (delta 0)
To git@github.com:rubyyot/chores.git
   ac60baa..9991b45  master -> master
rubyyot@atlas:~/working/chores$ git status
# On branch master
nothing to commit (working directory clean)

I made a small change and attempted to commit to github. It worked, yay!

rubyyot@atlas:~/working/chores$ vi README  #another small change to README
rubyyot@atlas:~/working/chores$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#       modified:   README
#
no changes added to commit (use "git add" and/or "git commit -a")
rubyyot@atlas:~/working/chores$ git add .
rubyyot@atlas:~/working/chores$ git commit -m "another small commit to test new remote to github"
Created commit a5b8a72: another small commit to test new remote to github
 1 files changed, 1 insertions(+), 1 deletions(-)
rubyyot@atlas:~/working/chores$ git push origin
fatal: protocol error: expected sha/ref, got '
*********'
 
You can't push to git://github.com/user/repo.git
Use git@github.com:user/repo.git
 
*********'

You will recall that earlier I removed my remote to my shared host and replaced it with github. Here I tried to push another small commit to it and it failed. I used my public clone url rather than my private ssh protected one, as I mentioned earlier. Time to fix that.

rubyyot@atlas:~/working/chores$ git remote rm origin
rubyyot@atlas:~/working/chores$ git remote add origin git@github.com:rubyyot/chores.git
rubyyot@atlas:~/working/chores$ git status
# On branch master
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores$ git push origin
Enter passphrase for key '/home/rubyyot/.ssh/id_rsa':
Counting objects: 5, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 330 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
To git@github.com:rubyyot/chores.git
   9991b45..a5b8a72  master -> master
rubyyot@atlas:~/working/chores$ git gc
Counting objects: 352, done.
Compressing objects: 100% (244/244), done.
Writing objects: 100% (352/352), done.
Total 352 (delta 105), reused 275 (delta 71)
rubyyot@atlas:~/working/chores$

All fixed up and I’m back in sync. I am still new to git and am only familiar with the handful of commands I use on a daily basis. There was probably a better or easier way to do what I did. If so, feel free to comment. If you are new to git too here are a couple resources.

How do I?

Below I included a small experiment I did where I cloned the chores source to a new local repository, checked out the post_9 tag to a new branch, modified origin to point to another repository, and checked the branch into master. Hopefully it will give you something to work with if you want to do anything similar. Alternatively, you could fork my github source into your own github repo and have fun.

 
rubyyot@atlas:~/working$ git clone   git://github.com/rubyyot/chores.git chores-github
Initialized empty Git repository in /home/rubyyot/working/chores-github/.git/
remote: Counting objects: 352, done.
remote: Compressing objects: 100% (227/227), done.
remote: Total 352 (delta 105), reused 320 (delta 88)
Receiving objects: 100% (352/352), 139.50 KiB, done.
Resolving deltas: 100% (105/105), done.
rubyyot@atlas:~/working$ cd chores-github/
rubyyot@atlas:~/working/chores-github$ git branch post_9_branch post_9
rubyyot@atlas:~/working/chores-github$ git checkout post_9_branch
Switched to branch "post_9_branch"
rubyyot@atlas:~/working/chores-github$
rubyyot@atlas:~/working/chores-github$ git remote rm origin
rubyyot@atlas:~/working/chores-github$ git remote add origin ssh://USERNAME@example.com/path/to/repo
rubyyot@atlas:~/working/chores-github$ git pull origin master
USERNAME@example.coms password:
From ssh://USERNAME@example.com/path/to/repo
 * branch            master     -> FETCH_HEAD
Already up-to-date.
rubyyot@atlas:~/working/chores-github$ git status
# On branch post_9_branch
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores-github$ git checkout master
Switched to branch "master"
rubyyot@atlas:~/working/chores-github$ git merge post_9_branch
Already up-to-date.
rubyyot@atlas:~/working/chores-github$ git status
# On branch master
nothing to commit (working directory clean)
rubyyot@atlas:~/working/chores-github$

Conclusion

In all it seems that things were not as bad as I had originally thought. Still it reinforces the need for discipline in doing TDD and coding in general. In the next post I will look over the health of the app and (hopefully) take appropriate steps to correct any problems.

Chores: a test driven website – Part 9 (the lost episode)

Sunday, May 10th, 2009

This is part of the Chores series of posts.

Well it’s been a while since National Testing month and there has been a lot going on both in my world and in the Ruby Community. I’ve been doing a good deal of reading, deployed a Monorail website and been generally busy. Unfortunately, I haven’t focused on building Chores, my test driven chore delivering website.

Today I’ve changed all that by dusting off chores and updating my gems. The first thing that I noticed was that I needed to add some configuration for Webrat for it to function within Cucumber. I needed to add the following to the env.rb file:

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

Next I see that I’m getting an error on the new action for Children because the @child variable is nil. Let’s write a functional test to fix that.

Here is my first pass at the functional test and controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'test_helper'
class ChildrenCanCrud < ActionController::TestCase
  tests ChildrenController
 
  specify "test that it should respond to index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:children)
  end
 
  specify "that it should pass child on new" do
    get :new
    assert_response :success
    assert_not_nil assigns(:child)
  end
 
  specify "that it should create child" do
    assert_difference 'Child.count' do
      post :create, :child => { :parent => Identity.make, :identity => Identity.make }
    end
 
    assert_redirected_to children_url
  end
 
end

./app/models/child.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Child < ActiveRecord::Base
  attr_accessor :open_identifier
  before_create :set_identity
 
  belongs_to :identity,             :foreign_key => :child_id,
                                    :class_name => 'Identity'
 
  belongs_to :parent,               :foreign_key => :parent_id,
                                    :class_name => 'Identity'
 
  validates_presence_of :open_identifier,  :message => 'can not be blank.',
                                           :on => :create
  validates_presence_of :identity,         :message => 'can not be blank.',
                                           :on => :update
  validates_presence_of :parent,    :message => 'can not be blank.'
 
  def set_identity
    self.identity = Identity.find_or_make_if_valid @open_identifier
  end
end

./app/models/identity.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class Identity < ActiveRecord::Base
  include OpenIdAuthentication
 
  validates_presence_of :identifier,        :message => "can not be blank."
 
  validates_uniqueness_of :identifier,      :message => "is already in use, please use another."
 
  validates_length_of :identifier,          :maximum => 100,
                                            :allow_nil => true,
                                            :message => "is too long."
 
  validate_on_create :valid_id
 
  has_many :parents,             :foreign_key => :parent_id,
                                 :class_name => 'Child'
  has_many :children,            :foreign_key => :child_id,
                                 :class_name => 'Child'
 
  def open_id
    return nil if self.identifier.nil?
    self.identifier[7..-2]
  end
 
  def identifier= identifier
    raise "Cannot update identifier." unless new_record?
    set_identifier identifier
  end
 
  def open_id= open_id
    raise "Cannot update open_id." unless new_record?
    set_identifier open_id
  end
 
  def valid_id
    errors.add(:identifier, "is not a valid Open ID.") if @invalid_id
  end
 
  def self.find_by_open_id open_id
    id = OpenIdAuthentication::normalize_identifier(open_id)
    find_by_identifier open_id
  end
 
  def self.find_or_make_if_valid open_id
    begin
      id = self.find_by_open_id Identity.normalize_identifier(open_id)
    rescue OpenIdAuthentication::InvalidOpenId
      return nil
    end
 
    unless id
      id = Identity.new :identifier => open_id
      id.save
    end
    id
  end
 
    def Identity.normalize_identifier id
    begin
      identifier = OpenIdAuthentication::normalize_identifier(id)
    rescue OpenIdAuthentication::InvalidOpenId
      identifier = nil
    end
    identifier
  end
 
  protected
  def normalize_id id
    identifier = Identity.normalize_identifier id
    unless identifier
      @invalid_id = true
      identifier = id
    end
    identifier
  end
 
  def set_identifier id
    write_attribute(:identifier, normalize_id(id))
  end
end

and

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ChildrenController < ApplicationController
  def index
    @children = Child.find(:all)
  end
 
  def new
    @child = Child.new
  end
 
  def create
    @child = Child.new(params[:child])
    if @child.save
      flash[:message] = "Child added."
      redirect_to children_url
    else
      flash[:error] = "Error saving Child."
      render :action =>  "new"
    end
  end
end

update to ./test/unit/child_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
require 'test_helper'
 
class ChildShouldBeValidated < ActiveSupport::TestCase
 
  specify "that a parent is required" do
    child = Child.new
    child.open_identifier = valid_open_id
    invalid child
    child.parent = Identity.make
    valid child
  end
 
  specify "that a child is required" do
    child = Child.new :parent => Identity.make
    invalid child
    child.open_identifier = valid_open_id
    valid child
  end
 
  specify "message for a null identity" do
    child = Child.new :parent => Identity.make
    validation_message_for child, :open_identifier, "can not be blank."
  end
 
  specify "message for a null parent" do
    child = Child.new :identity => Identity.make
    validation_message_for child, :parent, "can not be blank."
  end
end
 
class ChildShouldCreateIdentity < ActiveSupport::TestCase
 
  specify "that open_identifier is used to pass the OpenId" do
    child = Child.new :parent => Identity.make
    child.open_identifier = valid_open_id
    assert child.save
  end
end

./test/functional/children_controller_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require 'test_helper'
 
class ChildrenCanCrud < ActionController::TestCase
  tests ChildrenController
 
  def setup
    @identity = Identity.make
  end
 
  specify "test that it should respond to index" do
    get :index, nil, build_session_hash_for(@identity)
    assert_response :success
    assert_not_nil assigns(:children)
  end
 
  specify "that it should pass child on new" do
    get :new, nil, build_session_hash_for(@identity)
    assert_response :success
    assert_not_nil assigns(:child)
  end
 
  specify "that it should create child" do
    assert_difference 'Child.count' do
      post :create, {:child => { :open_identifier => "cheese.example.com" }}, build_session_hash_for(@identity)
    end
 
    assert_redirected_to children_url
  end
 
end

./test/test_helper.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require File.expand_path(File.dirname(__FILE__) + "/blueprints")
require 'test_help'
require 'machinist' #if you installed as gem rather than plugin
require 'faker'
 
class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  self.use_instantiated_fixtures  = false
 
  def invalid model
    assert !model.valid?
  end
 
  def valid model
    assert model.valid?
  end
 
  def validation_message_for model, column, message
    invalid model
    assert_equal message, model.errors.on(column)
  end
 
  def valid_identifier
    "http://test.example.com/"
  end
 
  def build_session_hash_for identity
    @session_hash = {'identity_id' => identity.id}
  end
 
  def valid_open_id
    "test.example.com"
  end
 
  def invalid_open_id
    "bad_id"
  end
 
end
 
def specify *args, &block
  test(*args, &block)
end

.. interestingly enough I find that my view that I wrote at some point expects a field called nickname

1
2
3
4
5
6
7
8
9
<% form_for(@child) do |f| %>
  <%= f.error_messages %>
  <%= f.label :nickname %>
  <%= f.text_field :nickname %>
  <%= f.label :open_identifier %>
  <%= f.text_field :open_identifier %>
  <%= f.submit "Add" %>
 
<% end %>

So I quickly create and run a migration to add the field.

~/working/chores (chores9)$ ruby script/generate migration add_nickname_to_child nickname:string
      exists  db/migrate
      create  db/migrate/20090402191006_add_nickname_to_child.rb

which can should be migrated with.

rake db:migrate

Here is the design I settled on.

As you can see, I’ve pushed it out to github. I’ve tagged it with “post_9″. I am hoping this will help those that want to follow along and reduce the amount of code that I need to post while writing this up. Maybe I’ll write up a post on git and github.

Update: I apologize this is all getting muddled in my brain as well. Between a change in rails versions and the passage of time. I hope to get this all straightened out in the next post.

Chores: a test driven website – Part 7 (return of the son of Chores)

Saturday, January 24th, 2009

This is part of the Chores series of posts

When last we worked on Chores, we had just generated two Models; Identity and Child. The idea behind these is that Child will serve as a many-to-many link between identities via has_many :through. First let’s look over the generated migrations and make a couple changes.

1
2
3
4
5
6
7
8
9
10
11
12
13
class CreateIdentities < ActiveRecord::Migration
   def self.up
     create_table :identities do |t|
       t.string :identifier, :null => false, :limit => 100
 
       t.timestamps
    end
  end
 
  def self.down
    drop_table :identities
  end
end

and

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CreateChildren < ActiveRecord::Migration
   def self.up
     create_table :children do |t|
       t.integer :parent_id, :null => false
       t.integer :child_id, :null => false
 
       t.timestamps
    end
  end
 
  def self.down
    drop_table :children
  end
end

and let’s migrate.

$ rake db:migrate (in c:/rails/chores)
==  CreateIdentities: migrating ===============================================
 -- create_table(:identities)    -> 0.0630s
==  CreateIdentities: migrated(0.0630s) ======================================
==  CreateChildren: migrating =================================================
 -- create_table(:children)    -> 0.0160s
==  CreateChildren: migrated (0.0160s) ========================================

and update the Blueprints (./test/blueprints.rb).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'machinist'  # if you installed the gem
 
Chore.blueprint do
  description "Put the dirty clothes in the hamper"
end
 
Identity.blueprint do
  identifier "#{Faker::Internet.user_name}.#{Faker::Internet.domain_name}"
end
 
Child.blueprint do
  child { Identity.make }
  parent { Identity.make }
end

And remember to add the reference to the Faker gem to ./test/test_helper

require 'faker'

I added not null constraints to the three columns in the two tables I created so I’m going to make note of that in the test files and start by making a child test (./test/unit/child_test.rb).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
require 'test_helper'
 
class ChildShouldBeValidated < ActiveSupport::TestCase
 
  specify "that a parent is required" do
    child = Child.new :identity => Identity.make
    invalid child
    child.parent = Identity.make
    valid child
  end
 
  specify "that a child is required" do
    child = Child.new :parent => Identity.make
    invalid child
    child.identity = Identity.make
    valid child
  end
 
  specify "message for a null identity" do
    child = Child.new :parent => Identity.make
    validation_message_for child, :identity, "can not be blank."
  end
 
  specify "message for a null parent" do
    child = Child.new :identity => Identity.make
    validation_message_for child, :parent, "can not be blank."
  end
 
  protected
    def invalid model
      assert !model.valid?
    end
 
    def valid model
      assert model.valid?
    end
 
    def validation_message_for model, column, message
      invalid model
      assert_equal message, model.errors.on(column)
    end
end

that fails spectacularly..

$ rake test:units (in c:/rails/chores)
c:/Ruby/bin/ruby -Ilib;test "c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/ rake_test_loader.rb"
"test/unit/child_test.rb" "test/unit/chore_test.rb" "test/u nit/identity_test.rb"
 
Loaded suite c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
 
Started
EEEE......
Finished in 0.766 seconds.
  1) Error: test_message_for_a_null_identity(ChildShouldBeValidated):
  ActiveRecord::UnknownAttributeError: unknown attribute: parent
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each'
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize'
    ./test/unit/child_test.rb:20:in 'new'
    ./test/unit/child_test.rb:20:in 'test_message_for_a_null_identity'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run'
 
  2) Error: test_message_for_a_null_parent(ChildShouldBeValidated):
  ActiveRecord::UnknownAttributeError: unknown attribute: identity
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each'
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize'
    ./test/unit/child_test.rb:25:in 'new'
    ./test/unit/child_test.rb:25:in 'test_message_for_a_null_parent'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run'
 
  3) Error: test_that_a_child_is_required(ChildShouldBeValidated):
  ActiveRecord::UnknownAttributeError: unknown attribute: parent
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each'
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize'
    ./test/unit/child_test.rb:13:in 'new'
    ./test/unit/child_test.rb:13:in 'test_that_a_child_is_required'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run'
 
  4) Error: test_that_a_parent_is_required(ChildShouldBeValidated):
  ActiveRecord::UnknownAttributeError: unknown attribute: identity
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2587:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'each'
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2583:in 'attributes='
    c:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:2283:in 'initialize'
    ./test/unit/child_test.rb:6:in 'new'
    ./test/unit/child_test.rb:6:in 'test_that_a_parent_is_required'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in '__send__'
    c:/Ruby/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in 'run'
 
10 tests, 9 assertions, 0 failures, 4 errors
rake aborted!
Command failed with status (1): [c:/Ruby/bin/ruby -Ilib;test c:/Ruby/lib/r...]
 
See full trace by running task with --trace)

Looks like I need to set up some attributes. Let’s try the following class definition

1
2
3
4
5
6
7
class Child < ActiveRecord::Base
  belongs_to :identity
  belongs_to :parent,               :class_name => 'Identity'
 
  validates_presence_of :identity,  :message => 'can not be blank.'
  validates_presence_of :parent,    :message => 'can not be blank.'
end

That gives us green

$ rake test:units (in c:/rails/chores)
c:/Ruby/bin/ruby -Ilib;test "c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/ rake_test_loader.rb" "test/unit/child_test.rb" "test/unit/chore_test.rb" "test/u nit/identity_test.rb" Loaded suite c:/Ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
 
Started
..........
Finished in 0.781 seconds
10 tests, 17 assertions, 0 failures, 0 errors

But wait!

If you have been following along in this series of posts, you might have noticed that I copied, pasted and then modified the code from the last test. Copy and Pasting is bad and the opposite of DRY right? Well, yeah. That’s right. And it’s also why it’s time to refactor. Here are the results of the refactoring.

./test/test_helper.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require File.expand_path(File.dirname(__FILE__) + "/blueprints")
require 'test_help'
require 'machinist' #if you installed as gem rather than plugin
require 'faker'
 
class ActiveSupport::TestCase
  self.use_transactional_fixtures = true
  self.use_instantiated_fixtures  = false
 
  def invalid model
    assert !model.valid?
  end
 
  def valid model
    assert model.valid?
  end
 
  def validation_message_for model, column, message
    invalid model
    assert_equal message, model.errors.on(column)
  end
end
 
def specify *args, &block
  test(*args, &block)
end

./test/unit/child_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
require 'test_helper'
 
class ChildShouldBeValidated < ActiveSupport::TestCase
 
  specify "that a parent is required" do
    child = Child.new :identity => Identity.make
    invalid child
    child.parent = Identity.make
    valid child
  end
 
  specify "that a child is required" do
    child = Child.new :parent => Identity.make
    invalid child
    child.identity = Identity.make
    valid child
  end
 
  specify "message for a null identity" do
    child = Child.new :parent => Identity.make
    validation_message_for child, :identity, "can not be blank."
  end
 
  specify "message for a null parent" do
    child = Child.new :identity => Identity.make
    validation_message_for child, :parent, "can not be blank."
  end
end

./test/unit/chore_test.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
require 'test_helper'
class ChoreShouldBeValidated < ActiveSupport::TestCase
 
  specify "that a description is required" do
    chore = Chore.new
    invalid chore
    chore.description = "Clean your room"
    valid chore
  end
 
  specify "that a description of 255 characters is valid" do
    chore = Chore.new :description => two_hundred_fifty_character_string
    valid chore
  end
 
  specify "that a description of 256 characters is invalid" do
    chore = Chore.new :description => two_hundred_fifty_one_character_string
    invalid chore
  end
 
  specify "message for a null description" do
    chore = Chore.new
    validation_message_for chore, :description, "Description cannot be blank."
  end
 
  specify "message for an overly long description" do
    chore = Chore.new :description => two_hundred_fifty_one_character_string
    validation_message_for chore, :description, "Description cannot be longer than 255 characters."
  end
 
  protected
    def two_hundred_fifty_character_string
      "This is a really long string that is the description of a chore and it is used to validate that the chore model will accept a string of 255 characters.  This string is longer than the longest string ever which also included some numbers 012345678901234567"
    end
 
    def two_hundred_fifty_one_character_string
      two_hundred_fifty_character_string + "1"
    end
end

As you can see, I’ve moved the duplicates to test_helper.rb, which clears up the duplicate code. Another option that you can explore to reduce duplication is mixins. That just about wraps up this post, next time I will work on testing the identity model.

Chores: a test driven website – part 6 (the son of)

Saturday, January 24th, 2009

This is part of the Chores series of posts

Last time we found the warm fuzzy feeling of green tests and a completed feature.   Unfortunately, while that is a great feeling, our website is far from complete.  We can however begin to leverage what we have learned so far to get this project moving.  I write up my next feature for cucumber and save it in the features directory as define_children.feature. 

Story:  Define child
As a parent
I want to define my child(ren)
So that I can assign them chores

Scenario: Defining a child
Given I am on the homepage
When I follow "Add Child"
And I fill in "child[nickname]" with "Bobby"
And I fill in "child[open_identifier]" with "bobby.example.com"
And I press "Add"
Then I should see "Child added."
And I should see "Bobby"

Scenario: Defining a second child
Given I am on the homepage
When I follow "Add Child"
And I fill in "child[nickname]" with "Bobby"
And I fill in "child[open_identifier]" with "bobby.example.com"
And I press "Add"
And I follow "Add another Child"
And I fill in "child[nickname]" with "Suzy"
And I fill in "child[open_identifier]" with "suzy.example.com"
And I press "Add"
Then I should see "Child added."
And I should see "Bobby"
And I should see "Suzy" 

Here are a few things that you can pick out of this feature.

  • It infers  that there is some sort of identity, probably with some sort of authentication at a future point.
  • Identities will likely have roles of parent and child.
  • The fact that the child is associated with an openID confirms that there will be authentication at some point.
  • Identites have either a one-to-many or a many-to-many relationship.

I think (at this point) that I’m going to leave the actual authentication out of the scope of the feature.  However it looks like there will be an Identity and some sort of relationship model in our near future.  Notice also that I took advantage of some of webrat’s built in step defintions.  I considered making a custom step to combine the entering of nickname, openId and pressing add, but I left it with the defaults for now.

Before we go ahead and create the models, let’s follow our feature and see where it leads us.  Cucumber’s output looks like:

$ cucumber features
Story:  Define child  # features/define_children.feature
As a parent
I want to define my child(ren)
So that I can assign them chores
  Scenario: Defining a child      # features/define_children.feature:6
    Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
    When I follow "Add Child"      # features/step_definitions/webrat_steps.rb:8
      Could not find link with text "Add Child" (RuntimeError)
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/flunk.rb:4:in 'flunk'

So we will need to modify the homepage to have a link to add child and to do that we will need to have a url to go to.   That means we need a controller.  Since the link we are making is to add a child let’s call it the Children Controller. 

$ ruby script/generate controller Children index new create
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/children
      exists  test/functional/
      create  app/controllers/children_controller.rb
      create  test/functional/children_controller_test.rb
      create  app/helpers/children_helper.rb
      create  app/views/children/index.html.erb
      create  app/views/children/new.html.erb
      create  app/views/children/create.html.erb

and update ./config/routes.rb to read

1
2
3
4
5
ActionController::Routing::Routes.draw do |map|
  map.resources :chores
  map.resources :children
  map.root :controller => 'home'
end

and finally update our homepage view (./app/views/home/index.html.erb) to read

views

1
2
<%= link_to "Add Chore", new_chore_path %>
<%= link_to "Add Child", new_child_path %>

Let’s check back with Cucumber and see how we did.

$ cucumber features
Story:  Define child  # features/define_children.feature
As a parent
I want to define my child(ren)
So that I can assign them chores
  Scenario: Defining a child      # features/define_children.feature:6
    Given I am on the homepage      # features/step_definitions/chores_steps.rb:1
    When I follow "Add Child"      # features/step_definitions/webrat_steps.rb:8
    And I fill in "child[nickname]" with "Bobby"      # features/step_definitions/webrat_steps.rb:12
      Could not find field labeled "child[nickname]" (RuntimeError)
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/flunk.rb:4:in 'flunk'

Not bad at all the step passed and it is now looking for the form on the new view.   Let’s make it by editing /app/views/children/new.html.erb to read:

1
2
3
4
5
6
7
8
9
10
11
12
13
<% form_for(@child) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </p>
 
  <p>
    <%= f.label :open_identifier %><br />
    <%= f.text_field :open_identifier %>
  </p>
  <%= f.submit "Add" %>
<% end %>

Let’s also remove ./app/views/children/create.html.erb file while we are at it, since we won’t need a view for this method.

Hmmm. hold on for a second. We are referencing an instance of @child that doesn’t exist. That’s definately going to fail. We need to add a model to define relationships between identities not to mention the identities themselves.

$ ruby script/generate model Identity identifier:string
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/identity.rb
      create  test/unit/identity_test.rb
      create  test/fixtures/identities.yml
      exists  db/migrate
      create  db/migrate/20090124055031_create_identities.rb

and

$ ruby script/generate model Child parent_id:integer child_id:integer
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/child.rb
      create  test/unit/child_test.rb
      create  test/fixtures/children.yml
      exists  db/migrate
      create  db/migrate/20090124055548_create_children.rb

When we return it will be time to edit the migrations and run them and then dive back into Test::Unit.

Chores: a test driven website – part 5 (the admission)

Friday, January 16th, 2009

This is part of the Chores series of posts

While committing Chores to git after the last refactoring, I’m thinking to myself. Who am I kidding? I’m going to write a post on functional testing? But I suck at functional testing! Well, we’ll see how it goes.

First, I’d like to install a gem called Machinist (this is also available as a plugin, if you prefer). Machinist is a drop in replacement for fixtures, allowing you to make (save) validated test data with a little config and a method call. You can read all about it in the readme, linked above. To install the gem simply:

$ gem install notahat-machinist

And be sure to configure your gem by adding the following to config/environments/test.rb

config.gem "notahat-machinist", :lib => 'machinist', :version => '0.3.1'

In fact, something I failed to cover is configuring your gems. Something you might want to take a minute to do.

Blueprints

Machinist uses “blueprints” to create records in the database for testing. These blueprints tell machinist how to create a valid record / object. These are stored in a blueprints file. So lets create a file in test/ called blueprints.rb that contains:

1
2
3
Chore.blueprint do
  description "Put the dirty clothes in the hamper"
end

and require the blueprint file in our test helper (test/test_helper.rb)

1
2
require 'machinist'
require File.expand_path(File.dirname(__FILE__) + "/blueprints")

On to the functional tests, or testing of the controllers. I used the rails scaffold functional tests as the starting point for my functional tests for chore. Here’s how the first run ended up. You might notice that there is not test for update, that’s because I’m not sure update is needed at the moment and since our cucmber feature (remember we are trying to get that working) didn’t say anything about updating, I’m just leaving it off for now.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'test_helper'
 
class ChoresCanCrud < ActionController::TestCase
  tests ChoresController
 
  specify "test that it should respond to index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:chores)
  end
 
  specify "that it should pass chore on new" do
    get :new
    assert_response :success
    assert_not_nil assigns(:chore)
  end
 
  specify "that it should create chore" do
    assert_difference 'Chore.count' do
      post :create, :chore => { :description => 'My Chore' }
    end
 
    assert_redirected_to chores_url
  end
 
  specify "that it will destroy a chore, muahahahahaha" do
    chore = Chore.make
    assert_difference 'Chore.count', -1 do
      delete :destroy, :id => chore.id
    end
    assert_redirected_to chores_url
  end
end

The corresponding Chores controller looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ChoresController < ApplicationController
  def index
    @chores = Chore.find(:all)
  end
 
  def new
    @chore = Chore.new
  end
 
  def create
    @chore = Chore.new(params[:chore])
    @chore.save
    redirect_to chores_url
  end
 
  def destroy
    chore = Chore.find(params[:id])
    chore.destroy
    redirect_to chores_url
  end
end

Additionally, I had to make a couple views to get the tests to run. They are skeletal at this point, barely more than an empty file. In fact ./app/views/chores/index.html.erb is an empty file and ./app/views/chores/new.html.erb is

1
2
3
4
5
6
7
8
9
10
11
<% form_for(@chore) do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :description %><br />
    <%= f.text_field :description %>
  </p>
 
  <%= f.submit "Add" %>
  </p>
<% end %>

Looking back at the functional tests, this seems a little on the light side. For instance the tests passed without ever checking to see what happened with save of Chore. I’m going to go back and add a couple mote tests. Adding the following two tests

1
2
3
4
5
6
7
8
9
  specify "that it should send a message that chore was created" do
    post :create, :chore => { :description => 'My Chore' }
    assert_equal "Chore added.", flash[:message]
  end
 
  specify "that it should send a message that chore creation failed" do
    post :create, :chore => { :description => nil }
    assert_equal "Error saving Chore.", flash[:error]
  end

changes the create event to be

1
2
3
4
5
6
7
8
9
10
  def create
    @chore = Chore.new(params[:chore])
    if @chore.save
      flash[:message] = "Chore added."
      redirect_to chores_url
    else
      flash[:error] = "Error saving Chore."
      render :action =>  "new"
    end
  end

Well that should be enough to move on with our cucumber feature and a solid beginning for our chore controller. Running rake features again shows us that we indeed have passed the next step. It is now looking for a field that isn’t there.

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore                       # features/define_chores.feature:6
    Given I am on the homepage                     # features/step_definitions/chores_steps.rb:1
    When I follow "Add Chore"                      # features/step_definitions/webrat_steps.rb:8
    And I fill in the chore with "My first chore"  # features/step_definitions/chores_steps.rb:5
      Could not find field labeled "chore[name]" (RuntimeError)
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/flunk.rb:4:in 'flunk'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/locators.rb:16:in 'field_labeled'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/locators.rb:10:in 'field'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/scope.rb:183:in 'locate_field'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/scope.rb:41:in 'fills_in'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/rails.rb:88:in 'send'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/rails.rb:88:in 'method_missing'
      c:/Ruby/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/integration.rb:498:in '__send__'
      c:/Ruby/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/integration.rb:498:in 'method_missing'
      ./features/step_definitions/chores_steps.rb:6:in 'And /^I fill in the chore with "(.*)"$/'
      features/define_chores.feature:9:in `And I fill in the chore with "My first chore"'
    And I press Add                                # features/step_definitions/chores_steps.rb:9
    Then I should see "Chore added."               # features/step_definitions/webrat_steps.rb:83
    And I should see "My first chore"              # features/step_definitions/webrat_steps.rb:83
 
 
1 scenario
2 steps passed
1 step failed
3 steps skipped

It appears that I had intended to name the field Chore.name, but when it came around time to name the field, I named it Chore.description. I’ll go ahead and modify my step definition to read ‘chore[description]‘ and try again.

Much better this time. Looks like the feature is looking for the flash message to be displayed on screen. Currently we aren’t doing that, so I’ll need to drop that in.

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore                       # features/define_chores.feature:6
    Given I am on the homepage                     # features/step_definitions/chores_steps.rb:1
    When I follow "Add Chore"                      # features/step_definitions/webrat_steps.rb:8
    And I fill in the chore with "My first chore"  # features/step_definitions/chores_steps.rb:5
    And I press Add                                # features/step_definitions/chores_steps.rb:9
    Then I should see "Chore added."               # features/step_definitions/webrat_steps.rb:83
      expected: /Chore added./m,
           got: "\n" (using =~)
      Diff:
      @@ -1,2 +1,2 @@
      -/Chore added./m
      +"\n" (Spec::Expectations::ExpectationNotMetError)
      ./features/step_definitions/webrat_steps.rb:84:in `Then /^I should see "(.*)"$/'
      features/define_chores.feature:11:in `Then I should see "Chore added."'
    And I should see "My first chore"              # features/step_definitions/webrat_steps.rb:83
 
 
1 scenario
4 steps passed
1 step failed
1 step skipped

Well, this isn’t beautiful but if I add this layout (as app/views/layout/application.html.erb), the flash will display and the feature step will pass and we can move on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Chores
</title>
</head>
<body>
 
<%= flash[:error] %>
<%= flash[:message] %>
 
<%= yield %>
 
</body>
</html>

that passes our next step, and we are onto the step that says ‘And I should see “My first chore”‘. This means that we are expecting it to have redirected us to index and that the new record that we just added should display on screen. Thus far I have placed nothing on the index view, so I will add the following.

1
2
3
<% @chores.each do |chore| %>
  <%=h chore.description %>
<% end %>

and now cucumber gives us some of it’s green cucumbery goodness with a passing test.

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore                       # features/define_chores.feature:6
    Given I am on the homepage                     # features/step_definitions/chores_steps.rb:1
    When I follow "Add Chore"                      # features/step_definitions/webrat_steps.rb:8
    And I fill in the chore with "My first chore"  # features/step_definitions/chores_steps.rb:5
    And I press Add                                # features/step_definitions/chores_steps.rb:9
    Then I should see "Chore added."               # features/step_definitions/webrat_steps.rb:83
    And I should see "My first chore"              # features/step_definitions/webrat_steps.rb:83
 
 
1 scenario
6 steps passed

Now I’ll commit to git and script/server to take a look at what our testing has created. It looks good, once I remove index.html from the public directory, I can see the Add Chore link from the home page. Clicking on it gives me a validated form that allows me to enter a chore entry and save it. We also have tests that tell us that it’s all working well and functioning as expected.

We have taken a look at implementing a feature with Test::Unit, webrat and cucumber and it’s really not all that difficult to do. Really the process from this point on is rinse, repeat, refactor. I’m not sure at this point if I’ll return to post on Chores or not. It’s been interesting documenting my process as I go along, but it’s also been a weight slowing me down. In any case, I hope you have enjoyed this series on test-driven development and maybe have decided to try it out for yourself. I hope so, and if you have kids, maybe check out chores when it’s done.