Rails Testing — Demystifying Test-Unit


Today we’re going to take the next step in our Rails education by examining the anatomy of Test-Unit, the core testing framework bundled with Ruby on Rails. We’ll pour over the directory structure to identify the different components of Test-Unit and see if we can figure out the intended purpose for each piece.

If you’re still wondering why we test, allow me to refer you to my last post and sum up with this: you can’t afford not to test. Your code might be good, but it isn’t perfect.


Test-Unit: DHH ’s favorite

When we create a new Rails app we get Test-Unit baked in. Rails is an opinionated framework, favoring convention over configuration, so the test structure is already defined out-of-the-box. But let’s start writing some code and see how all these pieces work together.

Having installed Rails (I’m at v3.2.6) we can create a new sample app like so:

$ rails new sample_app

Open up the sample_app directory. You should see file structure like this:

Test Directory Structure

Now let’s drop into the test folder and you should see these:

Okay, pretty sparse here, but these folders give you a prescriptive outlook on both how and what you should testing. They outline the kinds of tests you will write in order to achieve good code coverage and, ideally, help maintain a more holistic approach to testing.

Rake Test

For the sake of the Rails beginner, allow me to introduce you to:

$ rake test

Running this command will cause rake to spin up, load your test environment, look for tests to execute, find that you haven’t written any, and exit quietly. Rake itself isn’t part of Test-Unit but is leveraged (as a build tool) to make running batteries of tests easier. Here are a few variations on rake test so you can filter which tests are executed:

* rake test:units * rake test:functionals * rake test:integration

test_helper.rb

Before we talk about the different kinds tests let’s pop open test_helper.rb and discuss it’s terse innards. This is what it looks like by default:

Yep. That’s all there is to it.

This shouldn’t be perceived as a lack of control as much as it is a lack of clutter. At the most basic level this is all you need to start writing tests. We can put all sorts of configuration settings in here later, but, for now, relish the simplicity.

* lines 1-3 define the most basic environment variables, saying “Hey we’re going to run tests now – go load the test.rb file from the environments folder before we get started.” * lines 5-13 are the class definition for TestCase — an instance of which is created for each test written in your test suite and will serve as the starting-point for your tests. * fixtures :all — means that Rails will load all of your fixtures automatically for each test. What are fixtures? Be patient. We’ll get there in a moment.

test_helper.rb is included by the require statement in every test and it looks something like this:


Another option some developers prefer is to be a bit more explicit in their tests by defining a relative path to test_helper.rb like so:


The value added by being slightly more explicit is that a single test file can be run from the command line without loading all the additional overhead of rake.

$ ruby test/unit/other_mailer_test.rb

Fixtures

Fixtures provide a means of loading serialized data from YAML files for testing purposes. Tests frequently involve creating instances of various objects, making assertions about their state, and drawing conclusions from there. With fixtures you don’t have to manually create a new object instance for each test. You can simply load a fixture and refer to its value like so:

What they are: * Fast to load * Easy to generate from live data for later re-use * Easy to read What they are not : * Dynamic – you get the same values every time for better or worse * Aware of change – if your model’s change your fixtures may become outdated and require manual updates before your tests will pass Why use fixtures when I can use seeds.rb? * Using seeds.rb to accomplish the work of fixtures (or vice versa) would be a mistake as seeds.rb is designed for one purpose: to populate an empty database with essential data. To belabor the point, seeds.rb should be responsible for data so critical to your application that would be inoperable without it. For many applications it is likely you will need seeds.rb to setup your test database via rake db:test:prepare. What about using a factory? * Don’t fret. We’ll talk about factories (or more specifically, FactoryGirl) and how they can provide much needed functionality in a coming post.

Unit Tests

Unit tests are the bread and butter of testing. These little guys tell us that our code is operating as expected no matter what we throw at it. They are often closely paired to a model and follow a pattern like so: * /app/models/user.rb contains methods like:

  • def first_name
  • def last_name
  • def email
  • def password

* /test/unit/user_test.rb tests those methods:

  • def test_first_name
  • def test_last_name
  • def test_login
  • def test_password_reset

Unit tests make assertions that validate data going into a method and data coming out. They validate success as well as failure conditions.

Test-Unit has a large list of assertion matchers to help validate the behavior of a method. Tests providing coverage for crucial models (user.rb, for example) may have multiple _(def test_xyz_fails_if_nil, def test_xyz_resets_some_value)_ methods target a single model attribute (def xyz) .

Functional Tests

Functional tests are written to validate the content returned by individual actions within your controllers and mailers. Let’s look at some code.

Let’s talk about the body of these (almost) two tests.

* The first test calls get(:start) which sends an HTTP GET request to the RegisterController’s start method. * The expected result is a 200 response code. Anything other than a 200 response code would result in an error. That’s a pretty simple test but it accomplishes the functionality we need to test here. Testing functionality that takes place inside :start doesn’t belong here. We’ll talk about unit tests momentarily. * The second test creates a hash called params and passes the name/value pair :site_url => “www” to controller method api_domain_check . * Once again, the expected result is a 200 response code. You get the gist…

Integration Tests

Integration tests take functional tests one step further by examining behavior across multiple controller actions. A typical pattern might look like this: * visit page * check page for content, verify existence of particular elements or words * click on an element, fill in a form, interact… * check that page content has changed as expected

Performance Tests

Performance testing is quite unlike the other forms of testing. As such it is well outside of the scope of this document and I recommend you reading the official documentation here after you’ve got a good grasp on the other components of testing.

Focus on optimization after core functionality has been sufficiently delivered. Performance profiling, database tuning, and calculating cache hits/misses is otherwise premature. Fun, yes, but premature.

Wait, wait, where’s the rest?

You may be wondering why we didn’t talk about Cucumber , RSpec , or any of the other testing frameworks popular today and the reason is simple: we must start with the basics. Learning other ways of writing tests doesn’t necessarily lead to better testing or a better application. I’m not interested in arguing why one framework is easier to use than another. Such an argument is far too subjective to be profitable. What I will suggest is we first master the basics before we add unnecessary complexity to your application.

Where do I go from here?

Read through the Rails documentation and focus on getting a single test written against every method in your models. That’s step one. Once you’ve got your model’s being tested then it’s time to work on your controllers and mailers via functional tests. Logically, integration tests are next. We’re still missing a critical component of testing – one that’s not part of default suite – browser tests . It warrants a post of its own, so stay tuned.

Moving from cowboy coding (I test by hitting refresh on my browser hundreds of times during development) to Test Driven Development (I only open a browser when I need work on layout) is not a quick journey. But it’s one worth taking.

A Note About SimpleCov

There are a handful of gems available to help you assess your code coverage but I recommend simplecov . It’s easy to get setup, it produces a nice coverage document, and it’s being actively developed. The guys at Storm have already written a great tutorial to get your started.

_ What is code coverage ?_

HiringThing

Author: HiringThing

HiringThing is easy to use, intuitive online recruiting software that makes it easy to post jobs online, manage applicants and hire great employees.