Supercharged Rails Development

Eric Hodel | Thu, 20 Apr 2006 21:42:22 GMT

Geoff brings us a movie of autotest in action and has this to say:

My development process has recently been supercharged by autotest, a part of the ZenTest package.

[...]

The revolutionary part of this is that it speeds development by helping you develop without needing to open your web browser! I find myself thinking more about the functional issues that need to be solved rather than the placement of an image or the color of a link.

Posted in , ,  | no comments

Speeding up Test Runs with fork

Eric Hodel | Sun, 09 Apr 2006 03:47:00 GMT

Loading Rails takes a significant portion of your test run time, especially when you want to run only one test file or one test method. On my Powerbook loading Rails takes between four and six seconds. If you're frequently running unit tests this constant overhead can quickly become annoying.

When using autotest I may have to wait as much as ten seconds (five seconds between scans for changes, four seconds to load rails, one second to run the test) before I know if my changes fixed a problem or not. Ten seconds is past the threshold where I can keep paying attention which makes my mind wander. (A wandering mind is no good for productive work.) Also, those extra four seconds of loading Rails per test start to add up. I may load rails hundreds of times in a day just to run a tiny test.

There's one already existing way to reduce or eliminate that constant overhead of loading Rails. In development mode Rails reloads files to keep things running without restarting Rails on every change. I prefer to have an environment that is guaranteed to be clean when the tests start and reloading files removes this option.

Since I want Rails loaded without any application code I chose to create a process that would load rails then open up a server socket and wait for connections. When a connection comes in the process will fork to make a copy of the environment that can then load the application and run the tests.

A regular test run for just one file runs like this:

$ time ruby test/controllers/route_controller_test.rb Loaded suite test/controllers/route_controller_test
Started
......................................................
Finished in 13.192465 seconds.

54 tests, 268 assertions, 0 failures, 0 errors

real    0m17.884s
user    0m8.147s
sys     0m1.424s

The difference between the real time and the Test::Unit run time accounts for Rails and app loading overhead, about five seconds.

I've tentatively named the parent process spawner 'ruby_fork' and the client 'ruby_fork_client', so you start up the parent process:

$ RAILS_ENV='test' ruby_fork -r rubygems -e 'require_gem "rails"'
/Users/drbrain/Links/ZT/bin/ruby_fork Running as PID 3570 on 9084

ruby_fork understands -r, -I and -e just like regular ruby so I can just load Rails and none of the rest of my application.

Then I run ruby_fork_client which takes its arguments and passes them across to the child process and then reads from the socket and prints to STDOUT.

$ time ruby_fork_client -r test/controllers/route_controller_test.rb
Loaded suite /Users/drbrain/Links/ZT/bin/ruby_fork
Started
......................................................
Finished in 12.442556 seconds.

54 tests, 268 assertions, 0 failures, 0 errors

real    0m13.947s
user    0m0.077s
sys     0m0.022s

Now that extra time spent loading Rails is gone and I'm left with application loading and Test::Unit overhead which is miniscule in comparison.

ruby_fork is not Rails specific. The server and client can do anything they like, so this has applications beyond testing Rails (for example, handling incoming mail) or even Rails itself.

I'd like to release ruby_fork and ruby_fork_client as part of ZenTest but I'll be holding it until 3.3.0. Currently ZenTest is almost ready for release and ruby_fork and ruby_fork_client needs to act more like a regular invocation of ruby.

Posted in , , ,  | 5 comments

Pat Eyler on ZenTest, ZenTest-3.1.0

Eric Hodel | Wed, 29 Mar 2006 23:12:00 GMT

Pat Eyler has a new article in Linux Journal that covers the new additions to ZenTest.

We’ve just fixed a couple of his issues with ZenTest. ZenTest now ignores Emacs-generated autosave files and it has better error messages when you have missing files.

Go get ZenTest-3.1.0:

$ sudo gem install ZenTest

Posted in , ,  | 1 comment

Autotest is Better than Ever

Eric Hodel | Wed, 22 Mar 2006 21:27:35 GMT

Last night at Seattle.rb’s weekly hacking night I polished off several bugs and features for autotest in preparation for a release of ZenTest this week, possibly even today!

The most-interesting new feature (inspired by comments from Pat Eyler) is a mini continuous-integration mode I added. By running autotest -vcs=cvs, autotest will perform a cvs up every 5 minutes in the course of running its tests. Autotest also understands how to update svn and p4 repositories as well.

There is one open report related to testrb not being found when using the One-Click Ruby Installer. Not having a win32 machine, I can’t confirm whether the bug is ours, in the one-click installer, or a user configuration error.

Posted in , ,  | 1 comment

AWESOME

Eric Hodel | Mon, 13 Feb 2006 02:31:00 GMT

Breaking Rails’ functional tests into controller tests and view tests will allow easy auditing between the two types of tests.

But first I need to move all the view assertions out of my functional tests.

Posted in ,

Don't touch my CDATA

Eric Hodel | Mon, 21 Nov 2005 04:59:00 GMT

assert_tag parses CDATA

Posted in ,

Open-uri makes tests easy

Eric Hodel | Mon, 21 Nov 2005 01:28:00 GMT

Neither of the two Ruby Flickr APIs do what I want. One stomps all over the global namespace (Photo will be a model class, it adds a Photo class at the toplevel) and doesn’t have the photo taken date as one of its properties. The other doesn’t support the search method (you get back an XML blob instead of a collection) or the photo taken date.

Neither has tests, so I wasn’t going to figure out how to add what I wanted to either. Instead, I decided to wrap as much of the API as I needed with something that I could test.

On a whim, I decided to use open-uri instead of Net::HTTP. This made testing super-easy. I don’t have to touch the network at all, other than to get a blob of XML to feed into the test:

class Flickr

  attr_accessor :responses, :uris

  def open(uri)
    @uris << uri
    yield StringIO.new(@responses.shift)
  end

end

class FlickrTest < Test::Unit::TestCase

  def setup
    @flickr = Flickr.new 'API_KEY'
    @flickr.responses = []
    @flickr.uris = []
  end

Then a test simply adds the XML blobs it is supposed to receive from flickr up-front. After performing the request, I assert it attempted to fetch the correct URLs along with the rest of the stuff the method was supposed to do. (Search is paginated, and I wanted to fetch all the photos without making things clumsy for the user.)

  def test_photo_get_info
    @flickr.responses << <<-EOF
...
    EOF

    info = @flickr.photo_get_info :photo_id => 59864477

    assert_equal 1, @flickr.uris.length
    assert_equal 'http://flickr.com/services/rest/...',
                 @flickr.uris.first

    assert_equal Time.at(1131153707), info[:date_taken]
    assert_equal Time.at(1131153708), info[:date_uploaded]
  end

Posted in , ,

Rails Functional TestCase

Eric Hodel | Tue, 18 Oct 2005 07:26:00 GMT

Its a shame that Rails doesn’t define its own Test::Unit::TestCase subclasses. I’ve taken that into my own hands. This one puts Test on the front because that’s the Test::Unit way.

require 'test/unit'

def Object.path2class(klassname)
  klassname.split('::').inject(Object) { |k,n| k.const_get n }
end

class FunctionalTestCase < Test::Unit::TestCase

  def setup
    self.class.name =~ /\ATest(.*)\Z/
    return unless $1
    controller_klass = Object.path2class $1
    @controller = controller_klass.new
    controller_klass.send(:define_method, :rescue_action) { |e| raise e }
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new

    @deliveries = []
    ActionMailer::Base.deliveries = @deliveries
  end

  def test_stupid
  end

end

Posted in , , ,

I love unit tests

Eric Hodel | Sun, 18 Sep 2005 08:01:00 GMT

I just discovered we’ve had a23 broken RSS feeds since February!

Posted in , ,

Older posts: 1 2