Introduction
Several times recently, I have been asked how to develop a Rails application using the Test Driven Development approach. I'm not an expert here, but I've put together some notes on how to start working on a Rails application whilst being test-driven all the time.
As an example I will use a word-learning web application. The simplest use case is to display a random word object (with its Polish translation) from the database.
Every time we refresh we want to see a different word.
1. Create a new Rails application
rails my_app
cd my_app
Run tests with 'rake test'. It fails due to missing database configuration.
2. Set up the databases - config/database.yml
The code below assumes sqlite3 databases.
development:
adapter: sqlite3
database: db/my_app_development.sqlite
test:
adapter: sqlite3
database: db/my_app_test.sqlite
'rake test' now runs fine.
3. Create a Word class with a corresponding unit test
script/generate model Word
4. Write a unit test for the Word class. Edit the test/unit/word_test.rb.
def test_word_is_english_and_polish
word = Word.new :eng=>'never', :pl=>'nigdy'
assert_equal 'never', word.eng
assert_equal 'nigdy', word.pl
end
'rake test' now fails due to missing words table.
5. Edit db/migrate/001_create_words.rb
We are using a migration here in order to create a table. It's a recommended way of dealing with database changes.
def self.up
create_table :words do |t|
t.column :eng, :string
t.column :pl, :string
end
Word.new(:eng=>'yes', :pl=>'tak').save
Word.new(:eng=>'no', :pl=>'nie').save
Word.new(:eng=>'everything', :pl=>'wszystko').save
end
def self.down
drop_table :words
end
The sample words that we are adding use Word.new .. lines, will be added to the development database. It's important to distinguish the 'test' and 'development' database. The first one is only used during tests. The latter is used by default when you start the application.
Apply the migration with 'rake db:migrate'.
'rake test' now succeeds with the following:
'1 tests, 2 assertions, 0 failures, 0 errors'
6. Fixtures and test for word.random. Edit word_test again.
It's not easy to test a method which behaves randomly. Let's assume that it's enough to test that if we have only two words in our database then one of them should be called at least once per 10 calls.
fixtures :words
def test_random
results = []
10.times {results << Word.random.eng}
assert results.include?("yes")
end
Note the 'fixtures :words' line. Edit the 'words.yml' file.
yes:
id: 1
pl: 'tak'
eng: 'yes'
no:
id: 2
pl: 'nie'
eng: 'no'
This will be loaded to the test database before every run of tests.
7. Implement the Word.random method
def self.random
all = Word.find :all
all[rand(all.size)]
end
Warning: The code above could be slow for many words in a database (we retrieve all words only for selecting a random element). It's good enough for our needs.
8. Generate the Words controller with a 'learn' action
script/generate controller Words learn
9. Write a test for the learn method
Just as there is a one-to-one ratio between unit tests and models, so there is between functional tests and controllers. The Controller's responsibility is to retrieve objects from the Model layer and pass them to the View. Let's test the View part first. We use the 'assigns' collection which contains all the objects passed to the View.
def test_learn_passes_a_random_word
get 'learn'
assert_kind_of Word, assigns('word')
end
10. Make the Test Pass
def learn
@word = Word.new
end
11. Write more tests in the words_controller_test
How can we test that controller uses the Word.random method? We don't want to duplicate the tests for the Word.random method.
Mocks to the rescue! We will only test that the controller calls the Word.random method. The returned value will be faked with a prepared word.
Let's install the mocha framework:
gem install mocha
Now we can use 'expects' and 'returns' methods.
'expects' is used for setting an expectation on an object or a class. In this case we expect that the 'random' method will be called. We also set a return value by using 'returns' method. Setting a return value means faking (stubbing) the real method. The real Word.random won't be called. If an expectation isn't met the test fails.
require 'mocha'
def test_learn_passes_a_random_word
random_word = Word.new
Word.expects(:random).returns(random_word)
get 'learn'
assert_equal random_word, assigns('word')
end
'rake test' now fails. The Word.method wasn't called.
12. Rewrite the implementation
def learn
@word = Word.random
end
'rake test' now passes.
13. Test that a word is displayed:
Extend the existing test with assert_tag calls.
def test_learn_passes_a_random_word
random_word = Word.new(:pl=>'czesc', :eng=>'hello')
Word.expects(:random).returns(random_word)
get 'learn'
assert_equal random_word, assigns('word')
assert_tag :tag=>'div', :child => /czesc/
assert_tag :tag=>'div', :child => /hello/
end
14. Implement the view - learn.rhtml
<div>
<%= @word.eng %>
<%= @word.pl %>
</div>
15. Manual testing
script/server
Go to 'http://localhost:3000/words/learn'.
Refresh several times.
Related articles
... and some more TDD steps with Rails
Testing Rails controllers with mock objects
If you want to read more about testing in Rails go to the Guide To Testing The Rails.
32 comments:
Your code font is extremely hard to read... really detracts from what might be interesting reading. Perhaps it works on some platforms, certainly not on mine (unix with a reasonable selection of fonts)
Fine from Windoze. :-)
I don't think its a platform specific issue so much as it is a readability issue caused by choice of font, size, colour and background colour.
IMO too many people's blogs are a mess of too-tiny fonts. Case in point, this blog. If you changed your CSS to resize pre.code to 120 or 130%, and most importantly, changed the background and foreground colour, your code display would be far more readable to all - not just those using Cleartext.
pre.code {
font-size: 120%;
border: solid 1px #aaa;
padding: 6px;
background-color: #eee;
color: inherit;
overflow:auto;
margin: 10px 0px;
}
The above or color: #333; is far better IMO.
FYI, unrelated to this issue, your CSS includes various references to a font named "Vedana"; I'm sure you mean "Verdana".
Not all readers have the eyes of a 20 something any more.
Speaking of readability, fuzzyman: waiting for google adverts to pop up before getting to your content does nothing for the readibility of your content. Sorry to be so grumbly but I frequently avoid reading your material because I can't stand waiting for the google ad to clear.
Perhaps the google advert revenue is worth it to you but if that's your primary motivation, why not write directly for ad sales then?
Do you have a name 'Anonymous'.
As I said before - the combination of font size, family and colour (etc) is perfectly readable on Windows. So there must be *some* platform issue if you're finding it hard to read.
I don't think it is the adsense ads which are slow to load on my blog, I think it is a couple of the other javascript snippets (including amazon which I make very little revenue from - the google adverts I do quite well from).
There is a way to speed them up, simply loading the javascript *after* the content has loaded. I keep meaning to make the change - but haven't yet. Maybe I'll try and make time tonight.
Anonymous,
Thanks for the css hint. I changed the pre.code style to the one you suggested and it looks fine. I hope it's now better for you.
All the 'Vedana' stuff is generated by blogspot. I didn't change the template except for adding the pre.code style. It's a pain to nicely display any code here.
andrzej,
Thanks for the CSS update ... although I didn't intend for you to take my suggestion completely literally, down to the colour choices. The real issue is some combos - green on black might have been popular when display terminals attached to mainframes (and early PC's) had nothing but that to offer, but its never been the most readable, especially for aging eyes.
fuzzyman, as for "its perfectly readable on windows", there is an implied "for me" at the end of that statement.
I'm sure you aren't suggesting that just because *you* can read it (I have windows machines in my test lab, rest assured) that everything must be ok.
There's nothing wrong with the font rendering on my platform; I'd not checked which font had been employed when I first made the comment; its clear that its fg/bg colour combo that is the factor here.
fg/bg colour combos do matter:
http://hubel.sfasu.edu/research/survreslts.html
Generally speaking, dark on light is more readable. Having said that, I code in vim in a light on dark screen, but in order to make it work for me, I had to do some extensive testing on fonts and sizing.
http://www.lighthouse.org/accessibility/effective-color-contrast/
Here's a useful aide: Check the bottom of this post for a helpful bookmarklet which I've just dragged to my own toolbar...
http://www.456bereastreet.com/archive/200608/light_text_on_dark_background_vs_readability/
Sorry anonymous, I didn't read the bit about your eyes properly when I responded to your comment (although I wish I was still in my twenties)...
Nicely done introduction to testing in Rails. I've been getting familiar with Rails recently but have skipped testing until now. This post served as a perfect "getting feet wet" tutorial.
Mr Interested,
Thanks for a positive comment! I'm going to blog more about testing.
Looks shiny, but your example uses assert_tag, whereas nowadays assert_select is typically a much better option (and I believe assert_tag is deprecated).
In this case, I believe the code would be:
assert_select 'div', /czesc/
assert_select 'div', /hello/
But I may be misunderstanding what the assert_tag code is doing (this code checks for a div with content that matches /czesc/, and a div with content that matches /hello/).
Terrific tutorial.
To fix in Step 14: the rhtml is missing ampersands before "word".
Thanks for your tutorial! I live very much TDD!
I like your post. Testing seems to be easiest when you start with it from day one of an application. I like how your tests drive your development. (Your first step was to call rake test - which told you your database wasn't configured.)
Posts like these will only help increase the practice of writing good tests. Thats great because we all have a vested interests in good tests - you never know when you'll be called upon to work on an application someone else has built.
I carpool with a good friend and we 've made some tshirts that promote good programming practices like testing. Check out this shirt design and let us know what you think.
Happy testing - and thanks for the article.
Can you elaborate on how using a code generator fits with the TDD approach of "write the test, let it fail, then write the code to make the test pass", and having that drive the API design and the choice of what application behavior is implemented?
For example, why is the application even trying to connect to a database? What test code has driven the need to add DB code?
Aaron,
Cool t-shirts!
It took ma a while to explain my friend (a non-IT person) why a piece of code can be "guilty" :-)
matte, brian,
Thanks for nice comments!
Typo fixed.
Anonymous,
I see your point here.
First, this article doesn't cover all aspects of testing an application. Normally, I would start with an acceptance test (acceptance meaning something like a Selenium test). The acceptance test would describe how the user interacts with the system. This is a good place to test (indirectly) that we use something that stores data (like a relational database).
The second part of creating the Words application covers the Word.add_content method which is more related to databases.
As for the code generation, except for the application skeleton (rake file, logs directories) I don't really generate code here. All the script/generator calls generate only stubs for classes (class and method declaration).
Does it answer your questions?
Great tutorial. This is sound advice for starting some good habits with rails. You're right about so many blogs emphasizing the quickness of rails and neglecting the testing.
Congrats Andrew!
Thank to this tutor this is my first hand on experience with Rails/Ruby/Rake/Gem/Sqlite/mocha - Oh My! :-)
Excellent website. Good work. Very useful. I will bookmark!
Hello, Andrzej!
I find your post, as well as your blog, very interesting.
I'm starting to write a blog about rails and TDD as well.
I think your article it would be a great start for me.
My blog is at http://www.vip2web.com and is written in portuguese, so i'm here to ask you if i could translate this post and put into my blog?
Thanks in advance!
Best regards,
Vitor
Hi Vitor,
Sure, you can translate it to portuguese! Send me a link when it's done :)
Andrzej
On step 5, I'm getting this error:
[root@remandev migrate]# rake db:migrate
(in /usr/local/apache2/htdocs/testrails)
rake aborted!
uninitialized constant CreateWords
(See full trace by running task with --trace)
Just to verify, I'm supposed to replace the entire contents of the file db/migrate/001_create_words.rb with what is in the text field?
- Dave
black mold exposure,
black mold symptoms of exposure,
wrought iron garden gates,
your next iron garden gates, here,
hair styles for fine thin hair,
search hair styles for fine thin hair,
night vision binoculars,
buy, night vision binoculars,
lipitor reactions,
lipitor reactions,
luxury beach resort in the philippines,
beach resort in the philippines,
homeopathy for baby eczema.,
homeopathy for baby eczema.,
save big with great mineral makeup bargains,
companies marketing mineral makeups,
prodam iphone praha,
Apple prodam iphone praha,
iphone clone cect manual,
manual for iphone clone cect,
fero 52 binoculars night vision,
fero 52 night vision,
best night vision binoculars,
buy, best night vision binoculars,
computer programs to make photo albums,
computer programs, make photo albums,
Thanks for very interesting article. btw. I really enjoyed reading all of your postsabout Klamotten . It's interesting to read ideas about your stuff and Kleidung , and observations from someone else's point of view… makes you think more. Read more about Pattern and Crochet
Visit my Bodybuilding Muskelaufbau Shop . Best regards! Schwule - Gays
Insomnia... tension... irritability... weight gain... nervousness... fatigue... these are just a few of the uncomfortable and even painful symptoms most quitters experience. Just one or two of these is enough to have most people crying "uncle" and racing toward their hidden "emergency" pack of cigarettes. Once that happens, all hope of quitting smoking is lost. Nicocure® is a new generation aid to help you give up smoking. Nicocure® gives you the best possible chance of success and our workplace trials with thousands of smokers over ten years have demonstrated this. Bowtrol for Sensitive Digestion is an all-natural probiotics system that supports healthy colon function. Bowtrol helps you maintain a healthy
digestive tract, supports regularity, and promotes overall health and wellbeing.
Bowtrol Colon Cleanse is an all-natural herbal colon cleansing system that supports your body's natural design for eliminating
toxins. Gentle, effective colon cleansing today will have you feeling wonderful tomorrow.
Herbal pharmacy store provides You biggest selection of natural herbal products for skin care, male enhancement, stress management, anxiety treatment, weight loss, colon cleasing, quit smoking, pain, menopause relief, hair loss, hemorrhoids, wrinkle treatment, stretch mark removal, sleeping aid, bladder control, human growth hormones, human pheromones, lubricants, celllulite treatment and more. All major credit cards accepted and worldwide delivery!
Colon cleansing treatment product Bowtrol is an all natural herbal colon cleanser medication that has changed hundreds of thousands of people's lives. It's effective whole body safe and effective internal colon cleansing. Colon cleanser Bowtrol is formulated to maximize one's elimination without causing loose stools or uncomfortable cramping via frequent healthy bowel movements while assisting in cleansing the vital organs and lymphatic system.
Post a Comment