Ruby debugging with puts, tap and Hirb
I use puts heaps when debugging. Combined with tap, it’s pretty handy. You can jump right in the middle of a method chain without having to move things around into variables.
1 |
x = long.chain.of.methods.tap {|x| puts x }.to.do.something.with
|
I thought hey why don’t I merge the two? And for bonus points, add in Hirb’s table display to format my models nicely. These are fairly personal customizations, and aren’t specific to a project, so I put them in my own ~/.railsrc file rather than each project.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# config/initializers/developer_specific_customizations.rb if %w(development test).include?(Rails.env) railsrc = "#{ENV['HOME']}/.railsrc" load(railsrc) if File.exist?(railsrc) end # ~/.railsrc require 'hirb' Hirb.enable :pager => false class Object def tapp(prefix = nil, &block) block ||= lambda {|x| x } tap do |x| value = block[x] value = Hirb::View.formatter.format_output(value) || value.inspect if prefix print prefix if value.lines.count > 1 print ":\n" else print ": " end end puts value end end end # Usage (in your spec files, perhaps?) "hello".tapp # => hello "hello".tapp('a') # => a - "hello "hello".tapp(&:length) # => 5 MyModel.first.tapp # => # +----+-------------------------+ # | id | created_at | # +----+-------------------------+ # | 7 | 2009-12-29 00:15:56 UTC | # +----+-------------------------+ # 1 row in set |
Faster rails testing with ruby_fork
A long running test suite isn’t the problem. Your build server can take care of that. A second or two here or there, no one notices.
The killer wait is in the red/green/refactor loop. You’re only running one or two tests, and an extra second can mean the difference between getting into flow or switching to twitter. And you know what kills you in rails?
1 2 3 4 5 |
$ time ruby -e '' -r config/environment.rb real 0m3.784s user 0m2.707s sys 0m0.687s |
Yep, the environment. That’s a lot of overhead to be waiting for everytime you run a test, especially since it’s the same code every time! You fix this with a clever script called ruby_fork that’s included in the ZenTest package. It loads up your environment, then just chills out, waiting. You send a ruby file to it, and it forks itself (the process containing the environment) to execute that file. The beauty of this is that forking is really quick, and it leaves a pristine copy of the environment around for the next test run.
‘Environment’ doesn’t just have be environment.rb, for bonus points you can load up test_helper.rb, which will also load your testing framework into memory. In fact, you can preload any ruby code at all – ruby_fork isn’t rails specific.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ ruby_fork -r test/test_helper.rb & /opt/local/bin/ruby_fork Running as PID 526 on 9084 $ time ruby_fork_client -r test/unit/your_test.rb Started ... Finished in 0.565636 seconds. # Aside: this time is bollocks 3 tests, 4 assertions, 0 failures, 0 errors real 0m0.972s # This is the time you're interested in user 0m0.225s sys 0m0.035s |
That’s fantastic, though you’ll notice in newer versions of rails your application code is not reloaded. By default your test environment caches classes – which normally isn’t a problem except that newer rails versions also eager load those classes (so they’re loaded when you load enviornment.rb). You can fix this by clearing out the eager load paths in your test environment file:
1 2 |
# config/environments/test.rb
config.eager_load_paths = []
|
On my machine this gets individual test runs down from about 4 seconds to less than 1 second. You can sell that to your boss as a four-fold productivity increase.
Testing Glue Code
db2s3 combines together 3 external dependencies – your database, the filesystem, and Amazon’s S3 service. It has 1 conditional in the main code path (and it’s not even an important one). The classic unit testing approach of “stub everything” provides little benefit.
Unit testing is good for ensuring complex code paths execute properly, that edge cases are properly explored, and for answering the question “what broke?”. For trivial glue code, none of these are of particular benefit. There are no complex code paths or edge cases, and it will be quickly obvious what broke. In fact, the most likely thing to “break” (or change) over time isn’t your code, but the external services it is sticking together, which stubs cannot protect you from. Considering the high relative cost of stubbing out all your dependencies, unit testing becomes an expensive way of testing something quite simple.
For glue code, integration tests are the best solution. Glue code needs to stick, and integration tests ensures that it does. Here is the only test that matters from db2s3:
1 2 3 4 5 6 7 8 9 |
it 'can save and restore a backup to S3' do db2s3 = DB2S3.new load_schema Person.create!(:name => "Baxter") db2s3.full_backup drop_schema db2s3.restore Person.find_by_name("Baxter").should_not be_nil end |
This test costs money to run since it hits the live S3 service, but only in the academic sense. The question you need to ask is “would I pay one cent to have confidence my backup solution works?”
Always remember why your are testing. Unit tests are a focussed tool, and not always necessary.
Unique data in dm-sweatshop
dm-sweatshop is how you set up test data for your datamapper apps. Standard practice is to generate random data that follows a pattern:
1 2 3 4 5 |
User.fix {{ :login => /\w+/.gen }} new_user = User.gen |
Let’s not now debate whether or not random data in tests is a good idea. What’s more important is that the above code should make you uneasy if login is supposed to be unique. There was a hack in sweatshop that would try recreating the data if you had a uniqueness constraint on login and it was invalid, but it was exactly that: a hack. As of a few days ago (what will be 0.9.7), you need to be more explicit if you want unique data. It’s pretty easy:
1 2 3 4 5 |
include DataMapper::Sweatshop::Unique User.fix {{ :login => unique { /\w+/.gen } }} |
Tada! You can also easily get non-random unique data by providing a block with one parameter. Check the README for this and other cool things you can do.
Integration testing with Cucumber, RSpec and Thinking Sphinx
Ideally you would want to include sphinx in your integration tests. It’s really just like your database. In practice, this is problematic. Ensuring the DB is started and triggering a re-index after each model load is doable, if slow, with a small bit of hacking of thinking sphinx (hint – change the initializer for the ThinkingSphinx::Configuration to allow you to specify the environment). Here’s the rub though – if you’re using transactional fixtures the sphinx indexer won’t be able to see any of your data! Turning that off can really slow down your tests, and once you add in the re-indexing time you’re going to be making a few cups of coffee while they run.
One approach I’ve been taking is to stub out the search methods with RR. I know, I know, stubbing in your integration tests is evil. I’m being pragmatic here. For most applications your search is trivial (find me results for this keyword), and if you unit test your define_index block you’re pretty well covered. To go one step further you could unit test your controllers with an expect on the search method, or have a separate suite of non-transactional integration tests running against sphinx. I like the latter, but haven’t done it yet.
Enough talk! Here’s the magic you need to get it working with cucumber:
1 2 3 4 5 6 7 8 9 |
# features/steps/env.rb require 'rr' Cucumber::Rails::World.send(:include, RR::Adapters::RRMethods) # features/steps/*_steps.rb Given /a car with model '(\w+)' exists/ do |model| car = Car.create!(:model => model) stub(Car).search(model) { [car] } end |
Test setup broken in Rails 2.0.2
Some changes went into rails 2.0.2 that mean the setup method in test subclasses won’t get called. Here’s how it went down:
You can see some code illustrating the problem in 8445. This affects two plugins that we’re using – helper_test and activemessaging.
For the helper test, the work around is to rename your helper test setup methods to setup_with_fixtures.
1 2 3 |
def setup_with_fixtures super end |
For activemessaging, add the following line to the setup of your functionals that are failing (from the mailing list):
1 |
ActiveMessaging.reload_activemessaging
|
Testing rails
I was working on creating functional tests for some of my code today, a task made ridiculously easy by rails. To add extra value, I added an assertion (from Scott Raymond) to validate my markup against the w3c online validator:
1 2 3 4 5 6 7 8 9 10 |
def assert_valid_markup(markup=@response.body) if ENV["TEST_MARKUP"] require "net/http" response = Net::HTTP.start("validator.w3.org") do |w3c| query = "fragment=" + CGI.escape(markup) + "&output=xml" w3c.post2("/check", query) end assert_equal "Valid", response["x-w3c-validator-status"] end end |
The ENV test means it isn’t run by default since it slows down my tests considerably, but I don’t want to move markup checks out of the functional tests because that’s where they belong. Next step is to validate locally, which I’ve heard you can do with HTML Tidy.
Another problem is testing code that relies on DateTime.now, since this is a singleton call and not easily mockable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def pin_time time = DateTime.now DateTime.class_eval <<-EOS def self.now DateTime.parse("#{time}") end EOS yield time end # Usage pin_time do |test_time| assert_equal test_time, DateTime.now sleep 2 assert_equal test_time, DateTime.now end |
I haven’t found a neat way of resetting the behaviour of now. Using load 'date.rb' works but produces warnings for redefined constants. I couldn’t get either aliasing the original method, undefining the new one, or even just calling Date.now to work.
UPDATE: Ah, how young I was. A better way to do this is to use a library like mocha