Discover your missing specs

September 4th, 2007

Have you ever had the feeling that something was missing, but you weren’t quite sure what it was? I was looking at my code coverage the other day and thought to myself that it seemed like I was missing something. Rather than go through each file manually to see if it had an associated spec, I created a small rake task that does this for you.

The rake task looks at all the files in your app directory and tries to find an associated file in the spec directory. It follows the current Rspec generator naming conventions and if it doesn’t find the associated file it runs the generator to create it. It assumes that your app directory and spec directory follow the same directory structure and that you should at least have one test file for every application file.

You may find this as part of the Rake Tasks plugin over at Integrum.

To install:

script/plugin install http://svn.integrumtech.com/public/plugins/rake_tasks/

OR

piston import http://svn.integrumtech.com/public/plugins/rake_tasks/ vendro/plugins/rake_tasks

(Piston is one of my new favorite tools)

My additions are two rake tasks:

rake spec:check     # Check files in the app directory for corresponding test files in the spec directory.
rake spec:sync      # Check for missing test files in the spec directory and create them if they don't exist.

If you’re using Subversion, then you can also use the Subversion tasks that come with the Rake Tasks plugin to add the new spec files to your repository:

rake svn:add        # Adds new files to subversion
rake svn:remove     # Removes missing files from subversion
rake svn:ignore     # Configures svn:ignore properties on log, tmp, db/schema.rb and config/database.yml
rake svn:conflicts  # Resolves all svn conflicts by keeping the working file

This plugin was also the original home of some tasks that you may already be familiar with (I use them all the time):

rake db:create      # Creates the databases defined in your config/database.yml (unless they already exist)
rake db:drop        # Drops the database for your currenet RAILS_ENV as defined in config/database.yml
rake db:reset       # Drops, creates and then migrates the database for your current RAILS_ENV

Those three were merged into Rails edge in May of this year.

Thanks to Josh Knowles, Derek Neighbors, and Josh Huckabee for their feedback on the addition. If you have any comments, bugs, patches, questions, etc. please send them my way.

Firefox Profiles

August 26th, 2007

Firefox is my development browser of choice. With Firebug, Web Developer and ySlow, what more could any developer want? It is also my personal browser of choice and when I’m not developing then I don’t really need all those things, right? For personal browsing they will only slow things down. I suppose I could disable them every time… but no! There’s a better solution and it’s called Firefox Profiles.

Firefox profiles allows you to create multiple personas to use when starting Firefox. If you have Firefox, then you’ve already been using Firefox profiles, you just didn’t know it. Most people will probably only need the default profile and will never create others, but for web developers this is a must. So what do you do? It’s easy.

On your Mac (go here for Windows/Linux instructions), simply execute the following from the command line:

/Applications/Firefox.app/Contents/MacOS/firefox -profilemanager

A Firefox profile chooser will appear (it may appear behind your terminal window). This is where you manage your profiles. From this screen you may create, rename or delete profiles. There is also a checkbox that allows you to specify that the profile chooser should be consulted when starting Firefox. That’s not such a big deal. If you’re like me, you open Firefox from the Dock and the icon only allows a single instance. That’s okay, we’ll get around that in a second.

So, now you need to create some profiles. Here are the profiles I currently have:

  • Personal – This was originally the default profile; it has my personal stuff on it and of course Nads :)
  • Test User – A plain, vanilla Firefox instance; no extensions. This is what I use mainly for testing web applications. I didn’t have this one at first and had simply split personal from development. After seeing some strange behavior with applications when Firebug was installed, I decided I needed a profile that has no extensions to use for testing.
  • Debugger – Firefox with Firebug, Web Developer, and ySlow. This is what I use for development.
Firefox Profile Chooser

Firefox will keep these profiles separate, so if you want extensions in the new ones you will need to add them yourself. And don’t forget to remove all those unnecessary extensions from your personal profile. It will speed it up and get rid of that occasional Firebug interference (tabbing and return not working, etc.).

Now, how do we start multiple instances of Firefox? The way I do it currently is through a command line alias. This alias will open the Firefox profile chooser and then you’re off. To do this, you need to edit your .bashrc file. This file resides in your home directory. Open it in your favorite text editor, or if you don’t have a favorite then allow me to walk you through using vi. From the command line type:

vi ~/.bashrc

This opens the vi text editor. Type the letter

i
to enter input mode. Paste the following into the screen:

alias f='/Applications/Firefox.app/Contents/MacOS/firefox -profilemanager'

Press the Esc key to exit input mode. Type

:wq
to write the file and quit the editor. You should now be back at the command prompt, so the last thing we need to do is source the file. Type
. ~/.bashrc
and press enter. To try it out just type the letter
f
from the command line and then press enter. You should see the profile chooser appear. Now you may start multiple instances of Firefox from the command line. Note that you can only have one instance per profile open at a time.

Hopefully this makes your personal browsing and web development more enjoyable!

Striving for 100 percent

August 22nd, 2007

If it wasn’t obvious from my last two posts I’ve been doing a lot of testing. In fact, I spent almost the entire day yesterday writing tests. This got me thinking about the value of testing.

With the testing I’ve done recently, I feel like I’ve uncovered several problems that would have made it through to production. This is good. Testing first forces you to think about what you’ll be developing before you ever develop it. I think this has helped me to write better code and, to me, this has value.

However, when I consider the quest for attaining 100% code coverage I start to waffle on the value of that activity. It seems like a blind generalization such as “we must have 100% code coverage” is neither practical nor valuable. When it comes to coverage, it seems that high profile functionality should be well tested, probably 100% covered. High profile functionality being functionality that is executed easily and often, functionality with high consequences, etc. But does it really make sense to spend a lot of time trying to get code coverage on something that will probably never happen and where, even if it does, it has little to no impact on the system? To me, this doesn’t seem like a valuable activity. If it’s easy to test, then go for it. But if it starts to become a time-sink then it might be time to think about whether you’re really adding value.

If testing should be as easy to do as not, then should 100% really be the goal? Is not striving for 100% a cop-out?

Testing a helper with a block

August 21st, 2007

I’ve been doing a lot of Rspec testing lately. Although I’ve been using it for a while to do BDD I realize that I have not been testing things as well as I should. Yesterday I was trying to add tests for a helper method that ensures the passed in block is only output if the user is logged in and an administrator. It’s a fairly simple helper method.

def admin_accessible(&block)
  if logged_in? and current_user.admin
    concat(capture(&block), block.binding)
  end
end

I stumbled in the test though as I was unsure of how to test a helper that accepts a block. With the help of my colleague we came up with the following two solutions to this problem.

describe ApplicationHelper, 'invoke admin_accessible helper, when logged in as admin' do
  before(:each) do
    @user = mock("user")
    @user.stub!(:admin).and_return(true)

    self.stub!(:logged_in?).and_return(true)
    @block = "Testing admin_accessible" 
  end

  # Solution 1
  it 'shall return captured block' do
    html = eval_erb <<-ERB
      <% admin_accessible do %>
        <div><%= @block %></div>
      <% end %>
    ERB
    html.should have_tag('div', @block)
  end

  # Solution 2
  it 'shall return captured block' do
    _erbout = "" 
    html = admin_accessible do
       _erbout << "<div>#{@block}</div>" 
    end    
    html.should have_tag('div', @block)
  end
end

Do you know of a better solution?

I hadn’t created any tests of helper methods in Rspec until last week. Doing so I found that one of my helpers was invoking the restful_authentication logged_in? method. This probably isn’t a big thing, but I was unsure how to stub the logged_in? method, that is, how to get the controller context to be able to invoke the stub! method. What I learned from a colleague was that the context of the controller in a helper test is self.

self.stub!(:logged_in?).and_return(false)

The Ruby on Rails web application I’m finishing has an administrator interface that allows an admin to manage users, generate reports, etc. Some of these admin functions (like the reports) are not controlled by the admin controller. Of course, to actually test any of the controllers I need to be logged in as an admin. If the test doesn’t log in as admin, then it simply gets redirected to the login screen with a flash that you must be logged in to access that screen.

For testing the admin controller we’re fine. We can login as an administrator and proceed with testing the functionality. However, because the admin controller handles the login request it is not possible to login from any other controller. This happens because the HTTP post request does not allow you to specify the controller. It only allows you to specify an action. Fortunately for us there’s a workaround.

Like the Agile book suggests, I extracted my login method and placed it in the test_helper.rb file. It looks something like this:

def admin_login(login='admin', password='foobar')
    post :login, :admin => {:login => login, :password => password}
    assert_redirected_to :action => 'index'
    assert_not_nil(session['admin'])
    admin = Admin.find(session['admin'])
    assert_equal login, admin.login
end

To get the behavior we are looking for we must change this method to temporarily switch controllers. This can be accomplished as follows:

def admin_login(login='admin', password='foobar')
    old_controller = @controller
    @controller = AdminsController.new

    post :login, :admin => {:login => login, :password => password}
    assert_redirected_to :action => 'index'
    assert_not_nil(session['admin'])
    admin = Admin.find(session['admin'])
    assert_equal login, admin.login

    @controller = old_controller
end

So we temporarily switch over to the admin controller to perform the login, then we switch back when we’re done.

Know a better way? Share in the comments!

Ruby on Rails Code Coverage

January 24th, 2007

I realized that I would need to track my progress over time and began looking for a code coverage tool for Ruby on Rails. It did not take me long to find the coverage tool. However, coverage states that they are not making any changes and that rcov is better. I checked out rcov and it is pretty sweet and easy to use.

It replaces the command line invocation you would normally use to execute your tests and outputs the results as HTML into a coverage directory in your project. It is as easy as executing

rcov test/**/*.rb

to execute all of your tests and then browse the coverage directory with your favorite browser (i.e., Firefox).

If you are in the market for a free, easy to use code coverage utility for Ruby on Rails, I suggest rcov.

To install coverage
sudo gem install coverage
To install rcov
sudo gem install rcov

I finished the test for my models and it went pretty well. Today I started on the controller tests. Shortly after starting I ran into some curious Ruby on Rails behavior. I expected that the value of the flash variable would hold a certain value on an unsuccessful login attempt, but instead it was empty. I ran the server and tested it manually and there it was ‘Invalid login or password.’ appeared as an error flash on the page.

Turns out that flash.now is cleared after it is rendered in the view. That means you cannot get the value out in your tests. There is a workaround though. It is discussed on the Rails wiki and on Chu Yeow’s Redemption in a Blog. So what is the workaround?

You must access the render viewed contents and look for the corresponding flash div. For example, my code looked like the following:
flash.now[:error] = 'Incorrect login or password.'
Which outputs a div into the view like this:
<div id='error'>Incorrect login or password.</div>
In your test you can use the assert_tag assertion to find the tag corresponding to the element you want. Following our example above, we want to find a div tag with an id of error and contents should contain ‘Incorrect login or password’. This would look like:
assert_tag :tag => 'div',
           :attributes => { :id => 'error' },
           :content => 'Incorrect login or password.'

The wiki also talks about using @response.body but I have not looked in to that yet. I hope this helps.

Know of a better or alternate way? Share it in the comments.

Contracting

January 23rd, 2007

I am about to end my first contract position with the Gila Community College Wellness Center in Arizona. I have been developing an internal web-based application for them and will deliver it at the end of this week. I was able to convince them to let me use Ruby on Rails for the development with a MySQL Database. Essentially these are the same technologies I have been using on my startup.

Have I mentioned recently that I love Ruby on Rails? No? Okay, ummm… I love it. For the startup I was concentrating on pushing features as quickly as possible and I was least concerned about formal testing. In a contract position though I needed to really delve into how to test my code using the built in functionality provided with Ruby on Rails. With a little help from the Agile Web Development with Rails book I was testing in no time. The testing framework in place makes it very easy to develop and run your tests. Eventually I will need to go back through my startup code and add tests. I was dreading that activity, but now it seems like it will be easier than I expected. Of course, I am a bad programmer for letting it lapse in the first place… baaaad programmer!

Testing is one of those activities that is often overlooked during the course of your academic career. When I completed my BS in Computer Science at the UofA testing was not on the curriculum. Our introduction to testing was when some teaching assistant developed a rudimentary suite of tests to grade our program. They usually released some of them so you could get a feel for how well you had coded, but your grade was based on a super-set of the tests you were given. Now that I look back on it they probably knew only a little more than us about testing, but of course they needed something to base a grade on. I remember several times when people discovered errors in the TA’s tests!

There were very few people who developed their own tests. In fact, the philosophy I heard espoused most often from students and faculty was “Just get it working!” There was one guy though, Tal, who created tests and, compassionately, allowed others to test against them. He was a special case though as he was light years ahead of anyone else I knew in that program. Only later did I find out that he was a chess grandmaster... He eventually went on to pursue an advanced degree at UW and currently works at Google.

Anyway, I have wanted to try test-driven development for a while, but keep putting it off. Why? Why is it so hard to do? I think I have been conditioned to think of tests as a secondary activity. Sadly, this looks to be a difficult habit to overcome. Anyone have any suggestions for these testing withdrawal symptoms? Any advice is appreciated.