This is part of the Chores series of posts
I admit it, I don’t always write my tests first, and at other times I don’t write my tests at all. There I said it. However, I have found that writing tests first has accelerated the speed at which I learn and it has improved the quality of my work.
There is a general feeling that people who use TDD see themselves as better than those who don’t use it. I don’t think that this is the case. I think that this happens because once you start working in these styles, it’s strange to work without them. It’s not that you can’t or won’t do it, but it feels like you are working with a reduced toolset when you do.
I’m no expert at test driven development or similar styles, but the more I use them, the harder it is to imagine not being able to design and shape my objects with automated tests. I wouldn’t want to figure out how related objects fit together without the documentation that automated testing provides.
The term test driven fools people into thinking that it’s all about testing, but it’s not. It’s about creating functional designs with reduced dependencies. It’s also about being able to maintain your code. It’s about self improvement.
The point of learning to work in these styles is not to be better than those that don’t. Instead, the point is to make the code you wrote today better than the code you wrote a week ago, or a month ago. It is also so that when you or someone else looks at the code you wrote today, they will find it to be:
- working
- documented
- flexable
- extendable
There are many articles on the benefits of working in this style, and my point is not to repeat them. In fact my purpose is to prove them correct, or at least to show that it is not difficult to do and just takes a little discipline. The same discipline that I lack at times, but I’m getting better.
The Project
I am the father of 4 kids, and they are wonderful children who make messes and generally don’t clean up after themselves unless told to do so (and sometimes not even then). My general idea of a website is to built a Rails app that a family can go and if they are parents, keep track of what their kids do or don’t do. If they are kids, they can document what they have done and perhaps see much they have earned for the week in allowance. I have tried doing allowances before, but the problem is that my kids have to be told to do what they are supposed to be doing quite often to make sure they are doing it. I don’t want to do this. I want to provide immediate feedback for them on what they are earning, and I am hoping that this will give them the motivation they need to keep going. I also want to help them to develop the habits that will continue with or without the draw of rewards. It’s a moderately involved project,
Test or Die
Because January is Test Awareness Month I have decided to document bulding (at least beginning to build) this site on my blog using Cucumber and Test::Unit to drive it. I am developing currently on a Windows machine, though this may change as the project goes on as my other machines are running Ubuntu. So without further ado…
Note: (May 11, 2009 – I originally created this with Rails v2.2.2. I’m currently running back through to check compatability with Rails v2.3.2)
Start!
$ rails chores
create
create app/controllers
create app/helpers
.....
create log/development.log
create log/test.log
I have my rails app. The first thing I want to do is get this into source control. In my case git.
and
now I don’t want to store everything in source control. To to make git ignore files and or paths I create a .gitignore file in the chores directory and it looks like this…
what this does is it ignores the tmp and log directories and it also ignores any backup files which contain ~ or #.
Now I can check the status to make sure that the .gitignore file is working.
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# .gitignore
# README
# Rakefile
# app/
# config/
# doc/
# public/
# script/
# test/
nothing added to commit but untracked files present (use "git add" to track)
Followed by
$ git add .
$ git commit -am "initial commit"
Which adds my site to git’s index and commits my first commit. Now I’m going to backup my git repository to my shared host. A good explanation of this can be found here.
Now I already have cucumber, webrat and rspec installed as gems. In order to drive my development with cucumber features I need to set up my app to use cucumber.
$ ruby script/generate cucumber
create features/step_definitions
create features/step_definitions/webrat_steps.rb
create features/support
create features/support/env.rb
create features/support/paths.rb
exists lib/tasks
create lib/tasks/cucumber.rake
create script/cucumber
That does the trick.
Gotchas
I’ve found a couple gotchas at this point when working in Windows. The first is that cucumber doesn’t display the letter ‘a’ in the output if you don’t modify ./features/support/env.rb with the following line:
also you will want to make sure you are using a version of the sqlite gem that supports windows:
gem install sqlite3-ruby -v=1.2.3
What are your features?
Cucumber Features are a nice way to be able to document what you want your site to do and verify that it does it. Probably the easiest way do show this is to do it. I like to make features that aren’t too broad or two specific. I will start with one that is core functionality and save it in the ./features directory as define_chores.feature
Story: Define chores
As a parent
I want to define chores
So that I can assign them to my children
Scenario: Creating a chore
Given I am on the homepage
When I follow “Add Chore”
And I fill in "chore[name]" with “My first chore”
And I press "Add"
Then I should see “Chore added.”
And I should see “My first chore”
Now we have written our first feature. It is a pretty simple one, but as you can see it describes our intent in readable English. Now let’s fire up cucumber and see what we get.
Update: The following issue was fixed when I tried it in Rails 2.3.2 and cucumber 0.3.1.
$ cucumber features
c:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- webrat/rspec-rails (MissingSourceFile)
Hmmm.. what happened here. Cucumber uses the ./features/support/env.rb file for configuration let’s look there. Sure enough there is a require statement for ‘webrat/rspec-rails’. Let’s google it. Looks like the offending require is to add some webrat matchers to rspec. I’m just going to comment it out for now and try again.
Update: Using the features rake task (rake features from the command line) at this point in Rails 2.3.2 and cucumber 0.3.1 caused the following error:
rake aborted!
no such file to load -- /home/rubyyot/working/chores/db/schema.rb
This is because we have not yet defined any database tables. To fix this we simply have to create an empty file in the correct location. In Windows you can navigate to the folder and create a new file. In Linux you can type from your rails root:
This time I get:
$ rake features
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 on the homepage # features/step_definitions/webrat_steps.rb:6
No route matches "/" with {:method=>:get} (ActionController::RoutingError)
(eval):2:in '/^I am on (.+)$/'
features/define_chores.feature:7:in 'Given I am on the homepage'
When I follow “Add Chore” # features/define_chores.feature:8
And I fill in "chore[name]" with “My first chore” # features/define_chores.feature:9
And I press "Add" # features/step_definitions/webrat_steps.rb:14
Then I should see “Chore added.” # features/define_chores.feature:11
And I should see “My first chore” # features/define_chores.feature:12
1 scenario
1 failed step
1 skipped step
4 undefined steps
You can implement step definitions for undefined steps with these snippets:
When /^I follow “Add Chore”$/ do
pending
end
When /^I fill in "([^\"]*)" with “My first chore”$/ do |arg1|
pending
end
Then /^I should see “Chore added\.”$/ do
pending
end
Then /^I should see “My first chore”$/ do
pending
end
rake aborted!
Command failed with status (1): [/usr/bin/ruby1.8 -I "/usr/lib/ruby/gems/1....]
(See full trace by running task with --trace)
So what happened here?
Cucumber loads up all the features and all of the steps and then uses regular expressions to match the features to the steps. Then it executes the code associated with each step in order. Notice that it knew what “Given I am on the homepage” meant. That is because Webrat has already pre-loaded some common rails testing steps for us.
So what error are we having here? It says “No route matches “/” with {:method=>:get}”.
What are routes?
Routes in Rails have some pretty nifty tricks up their sleeves. Routes map an incoming request to an action on a controller. They live in config/routes.rb where you will find some good auto-generated documentation on how to configure them. In this case we need to create a route for our home page. This is accomplished by adding
map.root :controller => 'home'
somewhere after
ActionController::Routing::Routes.draw do |map|
and before
This tells rails that any requests that come in for the base url should be routed to the HomeController. Next time we can start working on building this object.