Monday, 18 August 2008

The Model Part 4 - Testing

Rails sets up the skeleton for unit testing when it creates a model. The skeleton is all set to run, though it does not actually test anything, with one trivial test case. However, there are a few steps of preparation:

Set up the "test" database
You need to do this whenever you make changes to the database (any time you do a migration basically). There is a rake task that will do this for you:
rake db:test:prepare

Note: Rake also supports db:test:clone and db:test:clone_structure; what db:test:prepare does is make a good guess as to which of those is best and does that.

Set up the fixtures
Rails generates a fixtures file for each model. A fixture is a file containing entries to populate the database before running each test. If you used the references type for any attributes in your model, you will need to have a belongs_to tag in your model, so that Rails will understand these to be my_model_id rather than my_model. You will get a SQL error otherwise complaining about an unknown column name.

Correct the require
If you are using NetBeans 6.1, you need to modify the require so Rails can find the test_helper file. require 'test_helper' should be require 'test/test_helper'. This has been corrected in NetBeans 6.5.

The default test should now work. In NetBeans you can right click in the file contents (i.e., the right pane), and choose test from the menu to just unit test a single file, or right click the project in the left pane and select test to run all unit tests.

Rails creates one test file for each model. I write one test method to test one use-case of a method, but I usually put in several assertions to check that the state is what I think it is before and after the method call.

Note that tests are run alphabetically. Also, be warned that if two tests have the same name, only one will be run, and you will receive no warning that one was not.

I find unit tests often involve significantly more code that the methods they test, and so most of the bugs are in the tests. However, once the bugs are gone, and the tests are passed, you do have a lot more confidence in your code. If you decide to rewrite the code, the tests should still work fine and are an excellent way to test your rewrite.

There is an issue with how comprehensive testing should be. If you have a method that manipulates a string, should you test if with nil, objects of a variety of other classes, string objects where the class has been modified? In Java or C# most of these are irrelevant.

Testing Exceptions
Use this:
e = assert_raise(RuntimeError) { my_code_that_raises }
assert_match(/Error message here/i, e.message)

From Jason Roelofs here:

Testing Helpers
Not really models, but they can and should be tested the same. Helpers are modules, so you just need to include them. Here is how:
require 'test/test_helper'

class AppHelperTest < ActiveSupport::TestCase

include ApplicationHelper

def test_my_helper_method

Testing private and protected methods
t is just as important to test these methods, but how do you access them from outside the class? In Ruby it is actually very easy to access private methods using the send method. This will accept your method name (as a symbol), followed by the appropriate arguments. Something like this:
assert_equal 'My expected result',
my_model.send(:my_private_method, arg1, arg2)

If you want to test your model with the parameters from a complex form, you can get Rails to build the hash for you. Throw an exception at the start of the controller method that will handle the submitted form, then, in your web browser, submit the form. Rails will hit the exception, and show an error page, which will include the POST data as a Ruby hash. Copy-and-paste into your test. Rails creates a special hash in which symbols and strings are equivalent for keys; chances are you will need to convert the strings to symbols (assuming you are accessing values with symbols).

There is an excellent series of blog pages on unit testing by Kevin Skoglund that starts here:
For easy reference, part ten lists the assertions available here:

Struggling with Ruby: Contents Page

No comments: