Importance of good Models

If you aren't entirely convinced about the importance of using Unit tests to test your Models after trying the steps found in the Importance of Unit Tests post, try these changes that continue on that example.

Again, Create an application folder

>rails testing

And again, Create a model, note* specify words to be a text string

testing>ruby script/generate model Display words:string

However, this time do NOT Edit the model file. But instead, Edit the migration file, db\migrate\###_create_displays.rb

def self.up
create_table :displays do |t|
t.column :words, :string, :default => 'no text entered', :null => false

t.timestamps
end
end

And then,
Create the table

testing>rake db:migrate

Populate the table with an entry

testing>ruby script/console
>>Display.create(:words => 'test text string')

Create a controller

testing>ruby script/generate controller Displays index

Edit the controller file, \app\controllers\displays_controller.rb

def index
@display_text = Display.find(:all)

respond_to do |format|
format.html # index.html.erb
end
end

Edit the view file, \app\views\displays\index.html.erb

<h1>Displays#index</h1>
<% @display_text.each do |d| %>
<p>
<%= d.words %>
</p>
<% end %>

Verify that everything is working so far.

testing>ruby script/server

Open your browser to http://localhost:3000/displays and you should see

Displays#index
test text string

Edit the unit test file, \test\unit\display_test.rb It should accept a text string entry, should have some text, and not be a numeric entry.

def test_should_create_entry
entry1 = Display.create(:words => 'more test text')
assert entry1.valid?
end

def test_words_should_not_be_nil
entry2 = Display.create(:words => nil)
assert entry2.errors.on(:words)
end

def test_words_should_not_be_empty
entry3 = Display.create(:words => '')
assert entry3.errors.on(:words)
end

def test_words_should_not_be_numeric
entry4 = Display.create(:words => 123)
assert entry4.errors.on(:words)
end

Edit the functional test file, \test\functional\displays_controller_test.rb

def test_should_show_index
get :index
assert_response :success
assert_template 'index'
assert_not_nil assigns(:display_text)
end

# :count is from fixtures file
# test\fixtures\displays.yml
def test_should_show_entries
get :index
assert_select 'p', :count => 2
end

Verify that the tests pass

testing>rake test:units

This time you should see*

1) ERROR:
test_words_should_not_be_nil(DisplayTest)
......

2) Failure:
test_words_should_not_be_empty(DisplayTest)
......
<nil> is not true.

3) Failure:
test_words_should_not_be_numeric(DisplayTest)
......
<nil> is not true.
4 tests, 3 assertions, 2 failures, 1 errors

*On my console the test_words_should_not_be_nil error shows many lines of the trace. Of particular interest is the line

./test/unit/display_test.rb:15:in `test_words_should_not_be_nil'

Again, although words was specified as a string, the test_words_should_not_be_numeric test fails.

And without the line

validates_presence_of :words

in the model file, the test_words_should_not_be_empty test also fails.

Importantly, the test_words_should_not_be_nil test does not fail, but this time errors. By looking at the log\test.log file we can find

[0;1mSQLite3::SQLException: displays.words may not be NULL: INSERT INTO "displays" ("updated_at", "words", "created_at") VALUES('YYYY-MM-DD hh:mm:ss', NULL, 'YYYY-MM-DD hh:mm:ss')

Try

testing>ruby script/console
>> Display.create(:words => )
SyntaxError: compile error
(irb):1: syntax error, unexpected ')'
from (irb):1
>> Display.create(:words => nil)
=> #<Display id: 2, words: nil, created_at .......>
>> Display.create(:words => '')
=> #<Display id: 3, words: "", created_at: .......>

What happened to the default value we specified in the migration file?

testing>ruby script/server

Open your browser to http://localhost:3000/displays and look at the view-source, you should see

<h1>Displays#index</h1>

<p>
test text string
</p>

<p>

</p>

<p>

</p>

Summary:
Without adequate data control in the model file, even though the database field was specified as a string, not NULL, and had a default value, it still accepted a numeric value and errored with a nil value. The default value was unused.
Without adequate unit testing, these bugs may have gone undetected until a later stage in the application's development.

Technorati Tags: , , ,