Archive for January, 2009

Chores: a test driven website – Part 8 (this time it's personal)

Saturday, January 31st, 2009

This is part of the Chores series of posts.

I’m now attacking the Idenitity model. My idea for this is a model that contains a single field. That field is a valid, properly formatted, immutable OpenID. Because I’m going to need to validate OpenIDs I’m going to install the Open ID Plugin as follows.

$ ruby script/plugin install git://github.com/rails/open_id_authentication.git
Initialized empty Git repository in c:/rails/chores/vendor/plugins/open_id_authentication/.git/
remote: Counting objects: 35, done.←[K
remote: Compressing objects: 100% (31/31), done.←[K
remote: Total 35 (delta 4), reused 21 (delta 2)[K
Unpacking objects: 100% (35/35), done.
From git://github.com/rails/open_id_authentication
 * branch            HEAD       -> FETCH_HEAD
$ rake open_id_authentication:db:create
(in c:/rails/chores)
      exists  db/migrate
      create  db/migrate/20090125184610_add_open_id_authentication_tables.rb
$ rake db:migrate
(in c:/rails/chores)
==  AddOpenIdAuthenticationTables: migrating ==================================
-- create_table(:open_id_authentication_associations, {:force=>true})
   -> 0.0310s
-- create_table(:open_id_authentication_nonces, {:force=>true})
   -> 0.0160s
==  AddOpenIdAuthenticationTables: migrated (0.0470s) =========================

Here is my test for the Identity model

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
require 'test_helper'
 
class IdentityShouldBeValidatedTest < ActiveSupport::TestCase
  specify "that an open ID is required on create" do
    identity = Identity.new
    invalid identity
    identity.open_id = "test.example.com"
    valid identity
  end
 
  specify "message for an overly long identifier" do
    identity = Identity.new :identifier => hundred_and_one_character_identifier
    validation_message_for identity, :identifier, "is too long."
  end
 
  specify "message for a null identifier" do
    identity = Identity.new
    validation_message_for identity, :identifier, "can not be blank."
  end
 
  specify "message for a poorly formatted" do
    identity = Identity.new :identifier => invalid_open_id
    validation_message_for identity, :identifier, "is not a valid Open ID."
  end
 
  specify "message for a duplicate identifier" do
    Identity.make :identifier => valid_identifier
    identity = Identity.new :identifier => valid_identifier
    validation_message_for identity, :identifier, "is already in use, please use another."
  end
 
  specify "that 100 character identifier is valid" do
    identity = Identity.new :identifier => hundred_character_identifier
    valid identity
  end
 
  specify "that identifier is unique" do
    Identity.make :identifier => valid_identifier
    identity = Identity.new :identifier =>  valid_identifier
    invalid identity
  end
 
  specify "that open_id must have the correct format" do
    identity = Identity.new
    identity.open_id = invalid_open_id
    invalid identity
  end
 
  specify "that open_id uniqueness extends to identifier when validated" do
    Identity.make  :identifier => valid_identifier
    identity = Identity.new :open_id => "test.example.com"
    invalid identity
  end
 
  protected
    def hundred_character_identifier
      "http://This.is.a.hundred.character.description.one.hundred.chatacters.is.not.too.long.but.it.of.com/"
    end
 
    def hundred_and_one_character_identifier
      "http://This.is.a.hundred1.character.description.one.hundred.chatacters.is.not.too.long.but.it.of.com/"
    end
 
    def invalid_open_id
      "bad_id"
    end
 
end
 
class IdentityOpenIdShouldSetIdentifierTest < ActiveSupport::TestCase
  specify "that open_id sets identifier" do
    identity = Identity.new
    assert_nil identity.identifier
    identity.open_id = "test.example.com"
    assert_not_nil identity.identifier
  end
 
  specify "that identifier sets open_id" do
    identity = Identity.new
    assert_nil identity.open_id
    identity.identifier = "http://test.example.com/"
    assert_not_nil identity.open_id
  end
 
  specify "that open_id is nicely formatted for display" do
    identity = Identity.new :identifier => "http://test.example.com/"
    assert_equal valid_open_id, identity.open_id
  end
end
 
class IdentityShouldBeImmutableTest < ActiveSupport::TestCase
  specify "that open_id is immutable" do
    identity = Identity.make
    assert_raise RuntimeError do
      identity.open_id = "monkey.example.com"
    end
  end
 
  specify "that identifier is immutable" do
    identity = Identity.make
    assert_raise RuntimeError do
      identity.identifier = "http://monkey.example.com/"
    end
  end
end
 
class IdentityShouldBeFoundByOpenID < ActiveSupport::TestCase
  specify "that open_id can be used to find a record" do
    identity = Identity.make :identifier => valid_identifier
    identity_from_db = Identity.find_by_open_id valid_open_id
    assert_equal identity.identifier, identity.identifier
  end
 
  specify "that display formatted open_id can be used to find a record" do
    identity = Identity.make :identifier => valid_identifier
    identity_from_db = Identity.find_by_open_id valid_identifier
    assert_equal identity.identifier, identity.identifier
  end
end

Here is the code that passes the test
./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
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 '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 valid_open_id
    "test.example.com"
  end
end
 
def specify *args, &block
  test(*args, &block)
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
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
 
  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
 
  protected
  def normalize_id id
    begin
      identifier = normalize_identifier(id)
    rescue OpenIdAuthentication::InvalidOpenId
      identifier = id
      @invalid_id = true
    end
    identifier
  end
 
  def set_identifier id
    write_attribute(:identifier, normalize_id(id))
  end
end

./test/blueprints.rb

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

This one took a little work to do and I’m going to revisit it in a little while to see if it needs any refactoring.

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.

Re: Mentoring Other Developers

Monday, January 19th, 2009

In response to Jeremy Miller’s post.  Sorry if this comes off as rantish, you have a good point and a good post.

I think that mentoring has great benefits.  It can also be frustrating.  Last year, a developer on my team was learning ASP.NET and C#.  Now I am not a master developer, but this new guy asked a series of questions that I knew the answers to. So I decided to invest some time in helping him steer clear of a number of pitfalls I fell into while I walked the path he was on.

Over the course of the next couple months, I answered many questions and additionally I did a few things he would not have been able to do on his own, including:

  • Getting him set up with an account on the dev IIS server.
  • Setting him up with a series of code reviews with developers on the team.
  • Telling other developers that I was working with him and therefore providing him with some credibility.

Unfortunately, over time I was seeing a pattern develop and repeat itself.   It went something like this.

Guy: I have a question.

Me: What can I help with?

Guy: Well, I’m having problems with X when I try to do Y.

Me: Ok, well that’s because of Z, You should do A instead.

Guy: why?

Me: <explanation, sometimes taking a long time>

… 1 week later ..

Guy: I have a question.

Me: What can I help with?

Guy: Well, I’m having problems with X when I try to do Y.

Me:  Well you should really be doing A, didn’t we already talk about this?

Guy: Yeah, we did, but I think doing it this way is better.

Me: But, as you can see, when you do it this way you run into issues.  You should do it with method A.

.. this happened more times than I wish to count.   This frustrated me to no end and made me feel that I had wasted my time. Not only that, it upset me that I put my credibility on the line for someone determined not to listen.  The conclusion that I come to out of this is that there is a certain maturity as a developer necessary from both the mentor and the mentee (?? sounds like mentos, the freshmaker).  But,  I’m not exactly sure how to tell if this is there or not.  I think it has something to do with my comments on professionalism and passion, i.e. that a developer must be passionate about their work to realize their full potential.

Also, there is a certain amount of failure that is necessary to get your bearings.  If you don’t fail, you don’t know where the boundaries are or what might be improved.  I’m not sure where the proper place for failure to end mentoring to begin is. Perhaps it’s just with a simple request for help.

Simple mocking with RhinoMocks

Monday, January 19th, 2009

Mocking frameworks and me

Until recently, mocking frameworks never really clicked in my mind.  I understood their purpose, however the terminology really didn’t fit with the idea of mocking to me and the tentative grasp I had on the concept would fall apart when I tried to get the mocking framework up and running in my tests.  So for a while I was mocking by hand.   Recently, I have been using Rhino Mocks successfully.  It has finally made sense to me, and I will mock by hand no longer.

The biggest mistake that I made in trying to understand the Rhino Mocks framework was thinking that all the mocking framework was doing was playing stunt double for the object that it was mocking.  Rhino Mocks doesn’t just ’stand in’ for the object you wish to mock, it will verify that methods are called on that object, giving you additional testing power.   Once I understood this, it explained the replay and verify terminology I would see in the examples, and things started to fall into place.

Once I had my epiphany about the usage of Rhino Mocks, I went to use it and I was stymied again.  I saw that I needed to create a mock object from an interface, but how?   And what is the difference between a StrictMock and a DynamicMock?   It wasn’t until I read this that I was able to begin actually using the framework.

As it turns out, the difference between a StrictMock and a DynamicMock is pretty simple to understand.  As we have seen, a core concept is the recording, playback and verification of method calls on the mock objects.  Well what happens if your System Under Test calls a method on the mock object that the mock wasn’t expecting?  What happens in this situation is the difference between Static and Dynamic Mock objects.  A StrictMock is just that, strict.  If a method gets called on a strict mock that it wasn’t expecting, it will let you know with red.  Now the terminology here still get me sometimes.  I think of the opposite of strict to be lax, forgiving or maybe flexable.  Likewise, I think of the opposite of dynamic to be static.  In this case, the opposite of strict is in fact, dynamic.  If a method is called on a dynamic mock that you didn’t tell it to expect, it just keep trudging along.  That’s not to say that it will necessarily return somehting valid, but the fact that the method itself  was called isn’t going to turn your test from green to red.

Making a mockery

So how do you actually use this beast?  Well it’s quite simple really, once you have all the pieces in place.   Having an understanding of interfaces and dependency injection will help get you off the ground here.  Let’s set up a couple of classes.

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
public class DeathTrap : IDrivable
{
  private int _speed;
  private bool _brakesFunctional;
 
  public DeathTrap()
  {
    _speed = 0;
    _brakesFunctional = true;
  }
 
  public void Accelerate(int amount)
  {
    _speed = _speed + amount;
    // Something really time consuming and irrelevant here
  }
 
  public bool Brake(int amount)
  {
    // Something really time consuming and irrelevant here
 
    if (amount == 21)
    {
      _brakesFunctional = false;
    }
 
    if (_brakesFunctional == false)
    {
      return false;
    }
    _speed = _speed - amount;
  }
}
 
public interface IDrivable
{
  void Accelerate(int amount);
  bool Brake(int amount);
}
 
public class Driver
{
  private IDrivable _prideAndJoy;
 
  public Driver(IDrivable prideAndJoy)
  {
    _prideAndJoy = prideAndJoy;
  }
 
  public void Go(int amount)
  {
    _prideAndJoy.Accelerate(amount);
  }
 
  public bool Stop(int amount)
  {
    return _prideAndJoy.Brake(amount);
  }
}

Ok so this is a basic driving game where a driver has a DeathTrap that implements IDrivable. If the driver tries to brake by 21 units the brakes will go out and will no longer function. So let’s test Driver and make sure it does what we would expect it to do. First let’s test that when we tell the driver to go, he goes.

1
2
3
4
5
6
7
8
9
10
11
12
[Test]
public void WhenITellADriverToGoTheCarWillBeAccelerated()
{
  mockery = new MockRepository();  //Create an object to hold and manipulate mocks.
  car = StrictMock<idrivable>();  //Tell Rhino Mocks to make a strict mock from the interface.
  driver = new Driver(car);  // inject the car into the driver via constructor.
 
  car.Accelerate(5);  // tell the mock to expect a method call to Accelerate with a value of 5.
  mockery.ReplayAll();  // switch the mocks to replay mode.
  driver.Go(5);   // The actual call.
  mockery.VerifyAll();  // verify that the expected call was in fact made.
}

As you can see there are a number of things happening here, but it really breaks down to the following.

  1. Setup Mock
  2. Hand the mock to the System Under Test
  3. Tell the mock what call to expect
  4. Flip the mock framework into replay mode
  5. Do the stuff you want to test
  6. Verify that the expected call was made

That wasn’t too bad. Let’s try another example. This one has a slightly different syntax. I could have used a StaticMock here, it really didn’t matter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Test]
public void WhenIPressTheBrakeWithAValueOfTwentyOneItWillReturnFalse()
{
  mockery = new MockRepository(); // Setup our mock repository
  car = DynamicMock<idrivable>();  //make the mock car
  driver = new Driver(car);  // inject car into our driver
 
  Expect.Call(car.Brake(21)).Return(false);  //tell our mock to return false when it gets a call to brake 21
  mockery.ReplayAll();  // switch to replay mode
  bool returned = driver.Stop(21);  // call the System Under Test
  mockery.VerifyAll();  // verify that the method on car was called
 
  Assert.False(returned);  // assert the return value is false
}

The primary difference here is that the method that we are calling returns a value this time. So we have to tell our mock what to return when the method is called. Notice that in both cases of this contrived example, by mocking the car we were able to do three important things.

  1. Isolate the behavior we were interested in.
  2. Ensure that the IDrivable returned the value we wanted.
  3. Avoided having to wait for Something really time consuming and irrelevant to happen.

I hope this helps get you off the ground with using a mocking framework. If I come across any further eureka moments I’ll be sure to post them. Enjoy!