Section 7.3. Testing in Rails


7.3. Testing in Rails

Rails extends the Test::Unit framework to include new assertion methods that are specific to web applications and to the Ruby on Rails framework. Rails also provides explicit higher-level support for testing by including a consistent method for loading test data and a mechanism for running different types of test.

7.3.1. Unit Tests, Functional Tests, and Integration Tests

In Rails, these three types of tests have very specific meanings that may differ from what you expect:

  • Unit tests are for testing models.

  • Functional tests are for testing controllers.

  • Integration tests are for testing higher-level scenarios that exercise interactions between controllers.

Look at your Photo Share application's directory tree, and you'll find that it contains a test subdirectory. All tests reside under this test subdirectory, which has several subdirectories of its own:



unit

Holds all unit tests.



functional

Holds all functional tests.



integration

Holds all integration tests.



fixtures

Contains sample data for all tests (more on this later).

Take a look at photos/test/unit , and you'll see that it already contains category_test.rb , photo_test.rb , slide_test.rb , and slideshow_test.rb . These are test case skeletons created by Rails when we generated our model classes. But before you can start filling these skeleton test files, you first need to understand Rails' environments and fixtures.

7.3.1.1. Environments

We software developers have always distinguished between code running in some form of development mode versus production mode. Development mode usually offers features such as active debugging, logging, and array bounds checking. These all add unnecessary overhead, so you should normally strip those conveniences out of your delivered production code.

This distinction of development versus production has usually been informal and ad hoc. As introduced in Chapter 2, Rails formalizes this practice using what it calls environments . Rails comes with three predefined environments: development, test, and production. You can also define new environments if you like, but most developers don't.

Each environment can have its own database and runtime settings. For example, in production mode, you usually want as much caching as possible to maximize performance, but in development mode, you want all caching disabled so that you can make a change and then immediately see it work. The predefined Rails environments have the default settings that make sense for each environment.

There are several ways to tell Rails what environment to use:

  • Set the operating system environment variable RAILS_ENV to 'development' , 'production' , or 'test' .

  • Specify the environment value in config/environment.rb with a line of Ruby code like this: ENV['RAILS_ENV'] = 'production' .

  • Use the -e option on the script/server script to start the WEBrick server. For example, script/server -e production starts the web server in production mode. Development mode is the default.

Take a look at the Photo Share application's config/environments directory and you will find three files: development.rb , test.rb , and production.rb . Each file contains the settings for its environment. These default environments are pretty well thought out, and it is unlikely that you will need to change them. But you should change the database settings for each environment. At the beginning of this book, we set up the development database, and now we need to set up the test database. Edit config/database.yml , and make sure that the test section looks like this:

 test:   adapter: mysql   database: photos_test   username: <your userid>   password: <your password>   socket: localhost 

Start the mysql command prompt ( mysql -u <username> -p <password> ). Then, create a database called photos_test :

 mysql>  create database photos_test;  Query OK, 1 row affected (0.05 sec) 

Now we can use a built-in feature of Rails to clone the database schema from the production database to the test database. Open a console window, navigate to the root directory of the Photo Share application, and run the command:

 >  rake db:test:clone_structure  

You now have a test database that is identical to the development database, except that the tables do not contain any data. Getting data into these tables to use in our test is what fixtures are all about.

7.3.1.2. Fixtures

Fixtures contain test data that Rails loads into your models before executing each test. You create your fixture data in the test/fixtures directory, and they can be in either CSV (comma-separated value) or YAML (YAML Ain't Markup Language) format.

YAML is the preferred format because it is so simple and readable, consisting mostly of keyword/value pairs. CSV files are useful when you have existing data in a database or spreadsheet that you can export to CSV format.

Fixtures for a particular database table should have the same filename as the database table name . So, to have fixtures for our photos database table, you would have a photos.yml file in the test/fixtures directory. Rails created a placeholder photos.yml when you created the photos model. Edit this existing test/fixtures/photos.yml file, and replace its contents with this:

 train_photo:   id: 1   filename:     train.jpg   created_at:   2006-04-01 03:20:49   thumbnail:    t_train.jpg   description:  This is a cool train! lighthouse_photo:   id: 2   filename:     lighthouse.jpg   created_at:   2006-04-02 14:58:49   thumbnail:    t_lighthouse.jpg   description:  My favorite lighthouse. 

YAML is sensitive to whitespace, so be sure to use spaces instead of tabs, and eliminate any trailing spaces or tabs. These same two fixtures in CSV format look like this in a photos.csv file (in CSV format):

 id, filename, created_at, thumbnail, description 1, train.jpg, "2006-04-01 03:20:49", t_train.jpg, "This is a cool train!" 2, lighthouse.jpg, "2006-04-02 14:58:49", t_lighthouse.jpg, "My favorite" 

In the YAML file, the first line of each fixture is a name that is assigned to that fixture. (A little bit later, you will see how you can use this name.) The remaining lines are keyword/value pairs, one for each column in the database table.

Now that we have a test database and some fixtures, we can actually start writing some tests.

7.3.1.3. Unit tests

In Rails, unit tests are for testing your models. The file photos/test/unit/photo_test.rb , for example, is where to create tests to test the Photo model. Rails created a skeleton of this file when we created the model. It currently looks like this:

 require File.dirname(__FILE__) + '/../test_helper' class PhotoTest < Test::Unit::TestCase   fixtures :photos   # Replace this with your real tests.   def test_truth     assert_kind_of Photo, photos(:first)   end end 

Let's walk through the code a line at a time:



require File.dirname(__FILE__) + '/../test_helper'

There are some serious Ruby idioms in this line of code, but the net result is to instruct Ruby to require (load) the file test_helper.rb from the parent directory ( photos/test ). test_helper.rb activates the Rails environment so that our tests are ready to run. __FILE__ is a special Ruby constant that contains the full path of the currently executing file. The File.dirname method takes that full path and removes the filename, returning only the directory path.



class PhotoTest < Test::Unit::TestCase

This code makes the PhotoTest class a subclass of Test::Unit::TestCase , as is required for running tests using Test::Unit .



fixtures :photos

This code tells Rails to load sample photo data into the database before each test (any existing data in the database is purged first). You can load multiple fixtures in one statement like this: fixtures :photos, :categories, slideshows .

It's finally time to create and run our first test. Edit photos/test/unit/photo_test.rb , and then add this code in the place of test_truth :

 def test_photo_count   assert_equal 3, Photo.count end 

This test is going to fail because it is asserting that the Photo database table contains three rows, but photos.yml contains only two. Lets try it and see. Open a command prompt, navigate to the root directory of our Photo Share application, and run this command:

 >  rake test:units  

You should see the following output:

 Started .F.. Finished in 0.313 seconds.   1) Failure: test_photo_count(PhotoTest) [./test/unit/photo_test.rb:7]: <3> expected but was <2>. 4 tests, 4 assertions, 1 failures, 0 errors 

Remember that the test/units directory contains four test files (even though we have modified only one of them), so this test ran all four. As expected, our test failed. Let's fix that:

 def test_photo_count   assert_equal 2, Photo.count end 

When you run the unit tests, you get:

 Started .... Finished in 0.359 seconds. 4 tests, 4 assertions, 0 failures, 0 errors 

You know that fixtures are used to populate our database tables. But you can also individually access each fixture's data using the fixture's name. [*] photos(:train_photo).attributes returns a hash containing all the keyword/value pairs for the TRain_photo fixture, so photos(:train_photo).attributes['id'] returns the value of the id property (which is 1 ). More interestingly, you can retrieve an entire fixture's entry from the database using its name:

[*] Only the YAML format allows you to name a fixture, so if you use the CSV format, you will not be able to do this.

 photo = photos(:train_photo) 

Retrieving the TRain_photo object from the database by name is the equivalent to retrieving it by id :

 photo = Photo.find(1) 

Let's use this feature to add another test to photos/test/unit/photo_test.rb :

 def test_photo_content   assert_equal photos(:train_photo).attributes['id'], 1   assert_equal photos(:train_photo), Photo.find(1)   assert_equal photos(:lighthouse_photo).attributes['id'], 2   assert_equal photos(:lighthouse_photo), Photo.find(2) end 

When you run the unit tests, you get:

 Started ..... Finished in 0.359 seconds. 5 tests, 8 assertions, 0 failures, 0 errors 

Before we move on to functional tests, let's write one more test that exercises our ability to perform basic CRUD operations with our Photo model. Once again, edit photos/test/unit/photo_test.rb , and add:

 def test_photo_crud   # create a new photo   cat = Photo.new   cat.filename = 'cat.jpg'   cat.created_at = DateTime.now   cat.thumbnail = 't_cat.jpg'   cat.description = 'This is my cat!'   # save it to the database   assert cat.save   # read it back from the database   assert_not_nil cat2 = Photo.find(cat.id)   # make sure they are the same   assert_equal cat, cat2   # modify this cat and update the database   cat2.description = 'A ghost of my cat.'   assert cat2.save   # delete it from the database   assert cat2.destroy end 

Let's run the test again and see whether this is going to pass:

 Started ...... Finished in 0.594 seconds. 6 tests, 13 assertions, 0 failures, 0 errors 

With our guilt suitably assuaged, let's move on to functional tests.

7.3.1.4. Functional tests

In Rails, you'll use functional tests to exercise one feature, or function, in your controllers. Functional and integration tests check the responses to web commands, called http requests . In this section, we work on functional tests for the photos controller.

We originally created our photos controller by generating scaffolding for it. When you generate scaffolding for a database table, Rails creates a remarkably complete set of functional tests:

 require File.dirname(__FILE__) + '/../test_helper' require 'photos_controller' # Reraise errors caught by the controller. class PhotosController; def rescue_action(e) raise e end; end class PhotosControllerTest < Test::Unit::TestCase   fixtures :photos   def setup     @controller = PhotosController.new     @request    = ActionController::TestRequest.new     @response   = ActionController::TestResponse.new   end   def test_index     get :index     assert_response :success     assert_template 'list'   end   def test_list     get :list     assert_response :success     assert_template 'list'     assert_not_nil assigns(:photos)   end   def test_show     get :show, :id => 1     assert_response :success     assert_template 'show'     assert_not_nil assigns(:photo)     assert assigns(:photo).valid?   end   def test_new     get :new     assert_response :success     assert_template 'new'     assert_not_nil assigns(:photo)   end   def test_create     num_photos = Photo.count     post :create, :photo => {}     assert_response :redirect     assert_redirected_to :action => 'list'     assert_equal num_photos + 1, Photo.count   end   def test_edit     get :edit, :id => 1     assert_response :success     assert_template 'edit'     assert_not_nil assigns(:photo)     assert assigns(:photo).valid?   end   def test_update     post :update, :id => 1     assert_response :redirect     assert_redirected_to :action => 'show', :id => 1   end   def test_destroy     assert_not_nil Photo.find(1)     post :destroy, :id => 1     assert_response :redirect     assert_redirected_to :action => 'list'     assert_raise(ActiveRecord::RecordNotFound) {       Photo.find(1)     }   end end 

These tests are in the file photos/test/functional/photos_controller_test.rb and cover the full range of CRUD operations. The Rails-generated functional tests for our other controllers are very similar.

You can run the functional tests with the command rake test:functionals but be forewarned that you will see a lot of errors! You might think that our Photo Share application has many problems, but the problem is that our tests are simply out of date. Those tests worked perfectly fine when they were first created and we were using the scaffolding for everything. But since that time, we have made lots of changes to the code yet never changed the tests to keep up with the evolving code base. Now we need to fix these tests.

For the purposes of this chapter, we are going to get the photo controller's functional tests working to give you enough understanding to fix the others yourself. To simplify the test reports , move all functional tests in photos/test/functional , except for photos_controller_test.rb , to another directory for safe keeping.

Because you can assign every photo to one or more categories, a lot of the photo controller code also works with categories. But we don't yet have any test categories, only test photos. So the first thing to do is to create some fixtures for the categories table and the categories_photos join table.

Edit the file photos/test/fixtures/categories.yml , and replace its contents with this:

 all:   id: 1   name: All people:   id: 2   name: People   parent_id: 1 animals:   id: 3   name: Animals   parent_id: 1 things:   id: 4   name: Things   parent_id: 1 

Now create the file photos/test/fixtures/categories_photos.yml with this content:

 train_category:   photo_id: 1   category_id: 4 lighthouse_category:   photo_id: 2   category_id: 4 

Finally, edit photos/test/functional/photos_controller_test.rb, and add these two lines at the beginning of the class definition for CategoriesControllerTest :

 fixtures :categories fixtures :categories_photos 

Let's try running our functional tests. From the base directory of our Photo Share application, run this command:

 >  rake test:functionals  Started F....... Finished in 0.469 seconds.   1) Failure: test_create(PhotosControllerTest) [./test/functional/photos_controller_test.rb:5 5]: Expected response to be a <:redirect>, but was <200> 8 tests, 25 assertions, 1 failures, 0 errors 

Hmmm: that wasn't exactly error-free; there was an assertion failure in the method test_create :

 def test_create   num_photos = Photo.count   post :create, :photo => { }   assert_response :redirect   assert_redirected_to :action => 'list'   assert_equal num_photos + 1, Photo.count end 

This test tries to create a new photo by posting a request to the create action of the current controller (which is the photo controller). We expected that the create action would save a new photo to the database and then redirect to the list action. Instead, we got an http 200 response (which is a normal, everything's OK, response).

A quick look at the create method shows that if the save to the database fails, then the controller renders and returns the new template, which correctly returns an http 200 response:

 def create   @photo = Photo.new(params[:photo])   @photo.categories = Category.find(params[:categories]) if params[:categories]   if @photo.save     flash[:notice] = 'Photo was successfully created.'     redirect_to :action => 'list'   else     @all_categories = Category.find(:all, :order=>"name")     render :action => 'new'   end end 

Why would the save to the database ( @photo.save ) fail? Let's take a look at the photo model ( photos/app/models/photo.rb ) to see whether that gives us any idea:

 class Photo < ActiveRecord::Base   has_many :slides   has_and_belongs_to_many :categories   validates_presence_of :filename end 

If you look closely, you'll see the culprit within the validation: validates_presence_of :filename . This code will refuse to save any instance of Photo to the database if it does not contain a filename; our test did not assign a filename. To fix that problem, edit photos/test/functional/photos_controller_test.rb to look like this:

 def test_create   num_photos = Photo.count   post :create, :photo => {:filename => 'myphoto.jpg'}   assert_response :redirect   assert_redirected_to :action => 'list'   assert_equal num_photos + 1, Photo.count end 

When you run the functional tests again, you'll see:

 >  rake test:functionals  Started ........ Finished in 0.468 seconds. 8 tests, 28 assertions, 0 failures, 0 errors 

Excellent. All the functional tests for the photos controller are now succeeding.

Did you notice that functional tests for the photos controller use a lot of assertions that are not part of Test::Unit but seem to be specific to web development ( assert_redirected_to ) and even specific to Rails ( assert_template )? Rails provides these additional assertions. Table 7-2 shows all of the extra assertions provided by Rails.

Table 7-2. Rails-supplied assertions

Assertion

Description

assert_dom_equal

assert_dom_not_equal

Asserts that two HTML strings are logically equivalent.

assert_generates

Asserts that the provided options can generate the provided path.

assert_tag

Asserts that there is a tag/node/element in the body of the response that meets all the given conditions.

assert_recognizes

Asserts that the routing rules successfully parse the given URL path.

assert_redirected_to

Asserts that the response is a redirect to the specified destination.

assert_response

Asserts that the response was the given HTTP status code (or range of status codes).

assert_routing

Asserts that path (URL) and options match both ways.

assert_template

Asserts that the request was rendered with the specified template file.

assert_valid

Asserts that the provided record is valid by active record standards.


7.3.1.5. Integration tests

Integration tests are a new feature in Rails 1.1. Integration tests are higher-level scenario tests that verify the interactions between the application's actions, across all controllers.

As you might have guessed by now, integration tests live in the test/integration directory and are run using the command rake test:integration .

Our Photo Share application hasn't yet been developed to the point where integration tests would be useful. Here, instead, is a hypothetical integration test to give you a feel for what they are like:

 require "#{File.dirname(__FILE__)}/../test_helper" class UserManagementTest < ActionController::IntegrationTest   fixtures :users, :preferences   def test_register_new_user     get "/login"     assert_response :success     assert_template "login/index"     get "/register"     assert_response :success     assert_template "register/index"     post "/register",          :user_name => "happyjoe",          :password => "neversad"     assert_response :redirect     follow_redirect!     assert_response :success     assert_template "welcome" end 

This test leads its application through the series of web pages that a new user would go through to register with the site. You can see that the scenario being tested is pretty easy to follow:

  1. Send an HTTP GET request for the /login page. Now check to see whether the request was successful and whether the response was rendered by the expected template.

  2. Simulate the user clicking on the "register" button or link by sending an HTTP GET request for the /register page. Again, check for the proper response.

  3. Simulate the new user filling out and submitting the registration form by sending an HTTP POST request that includes user_name and password field values. Now verify that the response is a redirect, follow the redirect, and verify that you successfully end up on the welcome page.

Integration tests can be used to duplicate bugs that have been reported . Then, when you fix the bug, you will know it because your test will start succeeding. Plus, you then have a test in place that will alert you if the same bug ever reappears.

7.3.2. Advanced Testing

Rails provides an impressive level of support for testing. But just in case that's not enough for you, here are a couple of third-party testing tools that are really on the cutting edge and worthy of your attention.

7.3.2.1. ZenTest

Self-described as "testing on steroids," ZenTest provides a set of integrated testing tools to automate and streamline your testing. For example, autotest monitors your projects files for changes. When autotest detects a change, it automatically runs the appropriate test to verify that the change has not broken anything.

You can learn more about ZenTest at http://www.zenspider.com/ZSS/Products/ZenTest/.

7.3.2.2. Selenium

Selenium is a testing tool written specifically for web applications. Selenium tests run directly in a browser, just as real applications do, provided it's a modern browser that supports JavaScript. As such, it is an ideal tool for testing the Ajax features of a web application.

You can learn more about Selenium on its home page at http://www.openqa.org/selenium/. IBM's developerWorks has a good article on using Selenium with Ruby on Rails at the following address: http://www-128.ibm.com/developerworks/java/library/wa-selenium-ajax/index.html.



Ruby on Rails[c] Up and Running
Ruby on Rails[c] Up and Running
ISBN: B003D3OGCY
EAN: N/A
Year: 2006
Pages: 94

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net