At the Forge - Testing Rails Applications with Shoulda
The past few months, I've been looking at a number of tools that make it easier for Ruby on Rails developers to improve the reliability of their software using automated testing. Even if you don't fully subscribe to the notion of test-driven development (TDD) or its cousin, behavior-driven development (BDD), the fact that Rails makes it so easy to test each part of your code makes it less likely that foolish mistakes will creep into your applications.
By default, Rails comes with Test::Unit, a test suite that makes it possible, and even easy, to check your code. Coupled with the test classes that come with actionpage, one of the core Ruby gems that comes with Rails, you can create a comprehensive test suite at the unit (model), functional (controller) and integration (cross-controller) levels. If you have a comprehensive test suite, you easily will detect, and understand the implications of, changes you make to the code that break the test.
That said, Test::Unit sometimes can be a bit verbose and repetitive. If you are writing unit tests, and you want to make sure that a particular attribute has been tested completely, it would be nice to be able to express a number of test cases quickly and tersely. Tests can function, in many ways, as a type of specification (as I will explain when we get to RSpec and Cucumber in coming months), and the easier it is to read these specifications, the less likely odd behavior is to slip through the cracks. It also goes without saying that the easier it is to write tests, and particularly comprehensive tests, the more likely you are to write them.
This is why Shoulda, a set of macros that work with Test::Unit, has become popular among Ruby developers in general and Rails developers in particular. Shoulda, developed by Tammer Saleh, a programmer who works for the Thoughtbot consulting company in Boston, is a set of macros that makes it easier to write tests with Test::Unit, as well as easier to read them. I have begun to use Shoulda with projects that I test with Test::Unit and have found it to be quite enjoyable.
This month, I take a look at Shoulda, and how you can integrate its macros into the tests you write in a Rails application. I explain how Shoulda divides tests into contexts, allowing you to group tests together even within a single file. I also describe how Shoulda's various macros make it easy to run a number of tests using a single readable line.
I should note that although Shoulda originally was designed to be used with Test::Unit and to provide something of an RSpec-like environment for Test::Unit users, it adds a growing amount of support for RSpec as well. Even if you use RSpec, you might want to consider using Shoulda along with your standard RSpec tests (or specs). I haven't looked at the combination for my own work, but it might be appropriate for what you're doing.
Shoulda comes packaged as a Ruby gem, and can be installed as:
sudo gem install thoughtbot-shoulda --source=http://gems.github.com
Earlier versions of Shoulda came packaged under a slightly different name (Shoulda, rather than thoughtbot-shoulda). It also is possible to install Shoulda as a Rails plugin; in this article, I assume that you have installed the gem version.
You can incorporate the gem in your configuration file, config/environment.rb:
config.gem "thoughtbot-shoulda", :lib => "shoulda", ↪:source => "http://gems.github.com"
With that in place, your Rails application either will run with Shoulda in place, or it will fail to load, complaining that the gem has not been installed. In one of my favorite Rails functions, you then can type:
rake gems:install
and your Rails application will examine its list of required gems, download those that are not yet on the system and install them in the appropriate places.
Let's assume you have created a simple Rails application that contains a single model that describes people. You can create it in the following way:
rails simple cd simple ./script/generate model Person firstname:text lastname:text ↪birthdate:date grade_in_school:integer phone_number:text ↪email_address:text rake db:migrate
At this point, you now have a simple Rails application (using the built-in default database, SQLite) with a single model defined. By creating your model with a generator, you get the following simple unit test file:
require 'test_helper' class PersonTest < ActiveSupport::TestCase # Replace this with your real tests. test "the truth" do assert true end end
True, you can invoke rake test on this, and the tests will succeed, but that's because the test is completely empty. You can write:
rake test:units
Now comes the hard part. What sorts of tests do you want to write? Well, that depends on the constraints you have put on your model, typically by using ActiveRecord validations.
Specifically, you presumably will want to make sure that the people have a first and last name, and that their grade in school (for the purposes of demonstrating some additional testing) is greater than 0 and less than 13. You will want to make sure that the person's birth date is in the past. You also will want to make sure that every e-mail address in the system is unique to avoid having more than one person with the same e-mail address.
In the model file itself, the validations will look like this:
class Person < ActiveRecord::Base validates_presence_of :firstname, :lastname, :email_address validates_uniqueness_of :email_address validates_numericality_of :grade_in_school, ↪:greater_than_or_equal_to => 0, :less_than_or_equal_to => 13 end
If you simply were using Test::Unit, you probably would want to test each of these validations. This has less to do with testing the validations and more to do with ensuring that your code meets the specifications you have laid out. (If tests were only a means of checking the correctness of your code, you could make a pretty good argument against tests for these validations, because ActiveRecord already has a fairly extensive test suite.)
If you were to try to test this line:
validates_presence_of :firstname, :lastname, :email_address
you would need to iterate over each of the three fields that are mentioned, checking to see whether the model would be valid if one of these were missing. See Listing 1 for an example of what person_test.rb, the file that contains the unit tests for the Person object, would look like just to test the need for each of those.
Listing 1. person_test.rb
require 'test_helper' class PersonTest < ActiveSupport::TestCase # Replace this with your real tests. test "working person" do person = Person.new(:firstname => 'First', :lastname => 'Last', :email_address => 'foo@example.com', :grade_in_school => 10) assert person.valid? end test "person must have first name" do person = Person.new(:firstname => '', :lastname => 'Last', :email_address => 'foo@example.com', :grade_in_school => 10) assert !person.valid? end test "person must have last name" do person = Person.new(:firstname => 'First', :lastname => '', :email_address => 'foo@example.com', :grade_in_school => 10) assert !person.valid? end test "person must have e-mail address" do person = Person.new(:firstname => 'First', :lastname => 'Last', :email_address => '', :grade_in_school => 10) assert !person.valid? end end
But, you lose something in creating these verbose tests. Instead of functioning as a checkup on your code, and as a specification of sorts for what you intend to do, these tests become verbose, repetitive and difficult to read.
With Shoulda installed, you now can remove all of the test cases that are shown in Listing 1, replacing them with one simple invocation:
should_validate_presence_of :firstname, :lastname, :email_address
Shoulda comes with a large number of macros that can help you test your ActiveRecord models in this way. For example, you can test all of the validations defined for the Person model using Shoulda macros:
should_validate_presence_of :firstname, :lastname, :email_address should_validate_uniqueness_of :email_address should_validate_numericality_of :grade_in_school should_ensure_value_in_range :grade_in_school, (1..12), ↪:low_message => 'must be greater than or equal to 1', ↪:high_message => 'must be less than or equal to 12'
Notice how the Shoulda macros' names reflect the names of the ActiveRecord validators. This was done after Shoulda was first released, which means that some of the documentation you see on-line might be slightly out of date and include deprecated macro names.
Also notice that in order to ensure that grade_in_school is numeric and that it is within a certain range, conditions that are set by a single validation line might sometimes require more than one Shoulda macro. In the particular case that I demonstrate here, there was a surprising mismatch between the error message that Rails gave to Shoulda and the message that Shoulda was expecting, in checking to see that the person's grade in school is in an acceptable range. In the end, I got around the problem by telling Shoulda what messages to expect from Rails. Although this is more verbose than I might have liked, it demonstrates the flexibility Shoulda offers.
Not surprisingly, Shoulda's authors make it possible for you to create your own macros, much as you might create your own validator method for an ActiveRecord class. I don't go into creating such macros here, but it is fairly well documented, and it means you can create a large number of tests, package them together under a single Shoulda macro and then use those tests (via the macro) across one or more projects.
Already, you probably can see how Shoulda macros can reduce the amount of code you need to write. Shoulda also provides an RSpec-like facility that makes it possible to name tests using strings, rather than method names. Granted, this is now included in Test::Unit, albeit using a slightly different syntax. But, you can define tests using the should keyword, rather than test, which adds a bit of readability—especially when used in conjunction with contexts, which I describe below.
Here, I create a single method in the model, fullname, which returns the concatenation of the person's first and last name:
def fullname # added to app/models/person.rb "#{firstname} #{lastname}" end
Next, I add a new test:
should "return the concatenation of the first and last name" do person = Person.new(:firstname => "First", :lastname => "Last", :email_address => "email@example.com") assert_equal person.fullname, "First Last" end
Now, there's nothing wrong with this test. It not only passes, but it also does a good job of checking that you are getting the right values back. Maybe it's just me, but I sometimes end up with very long lists of tests and end up categorizing them using comments inside the test file. Shoulda provides contexts that let you group tests within your file, using code rather than comments. It's obviously a bit silly to have a single context and a single test, but as with many things in the TDD/BDD world, it's worth doing things right even from the beginning, because you know that your codebase will grow over time, making it difficult to organize things correctly.
To define a context, you merely write:
context "Defined methods" do # "should" blocks go here end
In other words, you now can rewrite the test block as:
context "Defined methods" do should "return the concatenation of the first and last name" do person = Person.new(:firstname => "First", :lastname => "Last", :email_address => "email@example.com") assert_equal person.fullname, "First Last" end end
With a context block and a should block, you now can read your test as, “Defined methods should return the concatenation of the first and last name.” It's not the most amazing description in the world, but it's not a bad start. And besides, now you can add additional should blocks to test other defined methods.
A context may contain other contexts, as well as should blocks. This means that if you have a particularly complicated model you want to test, you can have a hierarchy of contexts, with should blocks at the bottom.
Moreover, using a context block means that you can write a setup block, which defines variables and otherwise allocates resources that will be used inside a should block. You could, for instance, now write:
context "Defined methods" do setup do @person = Person.new(:firstname => "First", :lastname => "Last", :email_address => "email@example.com") end should "return the concatenation of the first and last name" do assert_equal @person.fullname, "#{@person.firstname} ↪#{@person.lastname}" end end
As you can see, variables that are shared between a setup block and a should block need to be instance variables, their names preceded by an @ sign.
When a test is invoked, all the setup blocks within all of its surrounding contexts are invoked first. This means if a should block is within three nested contexts, and if each of those contexts has its own setup block, all three will fire before the test is executed.
If you are using Test::Unit to test your Ruby on Rails application, Shoulda is a natural fit, allowing you to write a large number of common tests using flexible, easy-to-read macros. In this article, I cover uses of Shoulda only for ActiveRecord models; other parts of Shoulda work with controller tests, providing additional features that can be of use for testers.
From my perspective, using Shoulda is a no-brainer. I have used it in a number of projects already and found that it further lowered the threshold to TDD/BDD, helping make my code that much more reliable. If you are new to testing, Shoulda is a great way to get started, providing an easy way to increase the stability and correctness of your code. All in all, Shoulda is a great resource for Ruby programmers in general and Rails programmers in particular.
Resources
The home page for Shoulda is thoughtbot.com/projects/shoulda. The documentation here is a good starting point, but you probably will need to play with it a bit to get the hang of things. Even the small problem I described above, in testing the minimum and maximum ages for a person, showed that you still might need to poke through the documentation to understand things fully.
A PDF cheat sheet for Shoulda is at kylebanker.com/assets/content/2008/shoulda_cheat_sheet.pdf, and the popular cheat sheet program for Ruby programmers also has an entry: cheat.errtheblog.com/s/shoulda.
The following are a few interesting blog posts about Shoulda that also can provide some useful ideas: pragdave.blogs.pragprog.com/pragdave/2008/04/shoulda-used-th.html, giantrobots.thoughtbot.com/2009/2/3/speculating-with-shoulda and www.alexjsharp.com/2008/10/15/shoulda-painless-unit-testing.
Reuven M. Lerner, a longtime Web/database developer and consultant, is a PhD candidate in learning sciences at Northwestern University, studying on-line learning communities. He recently returned (with his wife and three children) to their home in Modi'in, Israel, after four years in the Chicago area.