Mocha - A great test framework for Node.js 2

Posted by Bryan on December 05, 2011

I’ve been looking for a test framework for node.js for a while now that had all the features I was looking for.  Well, the search is over.  Mocha is hands-down the best I’ve used so far.  I like nodeunit, and have even contributed to it a bit, but the things I’ve been contributing are included in mocha.  It’s also quite a bit faster than nodeunit in my very crude benchmarks.

The key features for me:

  • Before all, after all, before each, and after each hooks.
  • Nice reporters (nested, spec style is my favorite — which is what I added to nodeunit).
  • The source code is clean and well-organized.  Nodeunit’s is good too, but I find it hard to follow sometimes.
  • Tests never hang (not sure what I’m doing wrong, but a lot of tests hang for me nodeunit).
  • Async and synchronous test support. I.e., you don’t need to pass in a callback function to individual tests or test hooks unless the test or hook has an async function in it.

It also has support for both “TDD” and “BDD” styles.  The Elders at MOG prefer the TDD syntax, so it’s nice having the ability to use that while reaping the benefits of BDD-like features.  It’s effectively the same exact thing as using the BDD syntax — it’s just using different words (’describe’ vs ’suite’, ‘it’ vs ‘test’).

A couple of drawbacks (or future features hopefully):

  • No support for recursively running all tests in a directory and its subdirectories.  This is solvable with a quick Makefile line though.
  • Can’t intertwine BDD and TDD styles (not without some ugly hacks anyway).  I.e., it would be cool if one guy on the team strongly preferred to use ‘describe’ instead of ’suite’, that Mocha would detect it somehow and let them run side-by-side with the TDD-style tests.

That’s a short list of minor complaints, folks.  I highly recommend trying it.

P.s. I’ve only barely used Jasmine, but it seems like there was some limitation (at the time anyway) that made it difficult to use with nodejs.  Could be totally wrong though.  A lot of people like Vows as well, but I had a hard time getting into that syntax, and it sure as hell wouldn’t fly with The Elders.

How to test has_and_belongs_to_many (HABTM) associations

Posted by Bryan on November 11, 2010

I recently inherited some code from someone who didn’t write any unit tests/specs, and was tasked with fixing a has_and_belongs_to_many bug where the IDs in the join table were backwards. E.g., if we had a cars table and tires table, and the join table was cars_tables, the car_id being set was actually the tire_id, and vice-versa. This was because we weren’t using the default association names — instead of the association tires being called by ‘cars.tires’, it was ‘cars.related_tires’. To do this, you pass extra params to the habtm method to specify the class_name, foreign_key, and association_foreign_key. It’s easy to mix up the two foreign key params, which is what happened here.

So I wrote some RSpec model specs to reproduce the bug and fix it. Turns out it’s a bit tricky reproducing the bug in tests. Let me demonstrate.

Here are the models with the wrong foreign_key and association_foreign_key params set in the habtm method:

class Car < ActiveRecord::Base
  has_and_belongs_to_many :related_tires,
   :join_table => 'cars_tires',
   :class_name => 'Tire',
   :association_foreign_key => 'car_id',
   :foreign_key => 'tire_id'
end
 
class Tire < ActiveRecord::Base
  has_and_belongs_to_many :related_cars,
   :join_table => 'cars_tires',
   :class_name => 'Car',
   :association_foreign_key => 'tire_id',
   :foreign_key => 'car_id'
end

And here is the RSpec test that I initially wrote:

describe Car do
  before(:each) do
    @car  = Car.create(:name  => 'Big Truck')
    @tire = Tire.create(:name => 'Big Tire')
  end
 
  it "should associate tire as related_tire" do
    @car.related_tires << @tire
    @car.related_tires.size.should == 1
    @tire.related_cars.size.should == 1
    @car.related_tires.first.should == @tire
    @tire.related_cars.first.should == @car
  end
end

The above spec passes. But I knew what was being written to the cars_tires table was wrong. I figured I could create a few extra Car objects initially, to make sure the @car.id and @tire.id aren’t both the same. I added this to the before(:each) block:

     3.times do 
      Car.create(:name => "Foo" + rand(100).to_s)
    end

But that still didn’t work. Since the foreign_key and association_foreign_key are backwards in both tables, the Ruby association methods return the correct results.

I was able to reproduce the bug by checking what is actually written to the join table:

 it "should set the correct car_id and tire_id in cars_tires table" do
    count_query = "select count(*) as qty from cars_tires"
    qty = Car.connection.select_one(count_query)['qty'].to_i
    qty.should == 0
    @tire.related_cars << @car
    @tire.related_cars.size.should == 1
    @tire.related_cars.first.should == @car
    Car.connection.select_one(count_query)['qty'].to_i.should == 1
    query = "select * from cars_tires"
    res = Car.connection.select_one(query)
    inserted_tire_id = res["tire_id"]
    inserted_car_id  = res["car_id"]
    inserted_tire_id.to_i.should == @tire.id
    inserted_car_id.to_i.should == @car.id
  end

Now the spec fails:

Car
  should associate tire as related_tire
  should set the correct car_id and tire_id in cars_tires table (FAILED - 1)

1)
'Car should set the correct car_id and tire_id in cars_tires table' FAILED
expected: 1,
     got: 4 (using ==)
./spec/models/car_spec.rb:32:

Finished in 0.168799 seconds

2 examples, 1 failure

And swapping the foreign_key and association_foreign_key values in the models fixes the problem.

So, if you use a non-default name for your HABTM associations, you might want to run an extra test like this to make sure it’s really doing what you think it is.

Rubystats 0.2.3 released

Posted by Bryan on July 06, 2008

Thanks to Franz Schwartau who noticed a bug in the Rubystats gem, and after four hours of debugging, I’ve released Rubystats 0.2.3 which fixes the bug* Franz found in the beta distribution calculations.

Thanks again, Franz.

 * By bug I mean bugs, and by bugs I mean stupid mistakes in my code and lack of sufficient test coverage.  But hey, this was some of the first ruby code I wrote (in fact I learned a lot of the ruby syntax in this project because I ported it from the PHPMath library), so I’m not surprised.  Not to say that the Rubystats library is junk.. it’s worked flawlessly for me for a few years now and has quite a bit of test coverage (more than PHPMath does — I implemented the tests from PHPMath and then some), but it’s certainly not perfect yet. It’s great to have members of the community finding bugs and letting me know about them.  That’s what open source is all about I guess.

Heel RubyGem

Posted by Bryan on April 25, 2008

I recently came across the Heel RubyGem. To quote the RDoc page, “Heel is a small static web server for use when you need a quick web server for a directory. Once the server is running, heel will use launchy to open your browser at the URL of your document root.” It uses Thin as a webserver

It’s just nice to, for example, launch Heel in your system’s rubygems directory so you can easily browse around. You can view ruby files with syntax highlighting (using coderay). Here’s a sample ruby file.. as you can see, it’s nicely done:

heel image

I was just looking the Thin webserver and came across this. It’s been handy so far.

Writing tests for acts_as plugins

Posted by Bryan on February 20, 2008

It took me a while to figure out a good way to approach this, so thought I’d share. I’ve been wondering how to best write test code for a Rails plugin that deals with ActiveRecord models, and after trying a few different approaches, I decided to do the thing I should have done in the first place: look at the test code for the plugins in Rails core. The acts_as_tree test code is what I ended up using as a guide for my tests for MultiFieldDate. This approach uses SQLite as an in-memory database, so you do have to have SQLite3 and the sqlite3-ruby gem installed for this to work. Alternatively, this should work with other database engines, but it would probably be a good idea to use table prefixes in those cases to avoid conflicts with existing tables.

Here’s a snippet of the MultiFieldDate test code:

require 'test/unit'
require 'rubygems'
require 'active_record'
$:.unshift File.dirname(__FILE__) + '/../lib'
require File.dirname(__FILE__) + '/../init'
 
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
 
# AR keeps printing annoying schema statements
$stdout = StringIO.new
 
def setup_db
  ActiveRecord::Base.logger
  ActiveRecord::Schema.define(:version => 1) do
    create_table :people do |t|
      t.column :name, :string
      t.column :birth_date, :date
      t.column :birth_year, :integer
      t.column :birth_month, :integer
      t.column :birth_day, :integer
    end
  end
end
 
def teardown_db
  ActiveRecord::Base.connection.tables.each do |table|
    ActiveRecord::Base.connection.drop_table(table)
  end
end
 
class Person < ActiveRecord::Base
  multi_field_date :birth, :date_field => 'birth_date', :year_field => 'birth_year',
  :month_field => 'birth_month', :day_field => 'birth_day'
end
 
class PersonTest < Test::Unit::TestCase
 
  def setup
    setup_db
  end
 
  def teardown
    teardown_db
  end
 
  def test_create_without_birth_date
    person = Person.new(:name => 'Joe Jones')
    person.save
    assert(person.valid?, true)
  end
#...
end

Upgrading to Rails 2.0 2

Posted by Bryan on February 16, 2008

Here’s a couple one-liner scripts to help convert your Rails app to 2.0. These scripts are by no means perfect, but should help if you have a lot of files with “start_form_tag”, “end_form_tag”, or “:post => true”. Back your files up first, of course.

find . |grep -i "\.erb$" | xargs ruby -p -i -e "gsub(/<%=.*start_form_tag(.*)%>/i, '<% form_tag \1 do -%>')"
find . |grep -i "\.erb$" | xargs ruby -p -i -e "gsub(/<%.*end_form_tag.*%>/i, '<% end #form_tag-%>')"
find . |grep -i "\.erb$" | xargs ruby -p -i -e 'gsub(/:post\s=>\strue/i, ":method\s=>\s:post")'

Replace “erb” with “rtml” if you haven’t converted your views to .erb yet.

Responses to benchmarks

Posted by Bryan on February 06, 2008

A couple people posted responses to the benchmarks on my Sun blog, so here they are.

From Susan Potter:

Bryan,

I wrote a benchmarking blog post in October last year relating to the difference in code-level optimizations between Ruby 1.8.6 and Ruby 1.9.0 and how the roles were reversed in these two versions: (link)

And her direct response:

This is a response to Bryan Donovan’s blog post called ways to pass options.

Bryan’s original benchmark was run on Ruby 1.8.4. I will be running the benchmarks on Ruby 1.8.6 and Ruby 1.9 to demonstrate performance reversal of code level optimizations between Ruby versions (as I did in Ruby performance reversal benchmark 1.8 to 1.9).
I will use Bryan’s Ruby benchmark code to run for both Ruby 1.8.6 and Ruby 1.9.0 (official).

Ruby 1.8.6 Results

Below are the results of running the options passing benchmark in Ruby 1.8.6:

With no option values passed
Rehearsal -------------------------------------------
delete:   0.290000   0.050000   0.340000 (  0.428564)
merge:    0.700000   0.190000   0.890000 (  1.017415)
or_nil:   0.370000   0.050000   0.420000 (  0.509209)
---------------------------------- total: 1.650000sec
 
              user     system      total        real
delete:   0.300000   0.050000   0.350000 (  0.428401)
merge:    0.760000   0.130000   0.890000 (  1.010474)
or_nil:   0.360000   0.050000   0.410000 (  0.490170)
 
With some option values passed
Rehearsal -------------------------------------------
delete:   0.360000   0.060000   0.420000 (  0.505094)
merge:    0.890000   0.130000   1.020000 (  1.150165)
or_nil:   0.390000   0.070000   0.460000 (  0.563713)
---------------------------------- total: 1.900000sec
 
              user     system      total        real
delete:   0.340000   0.060000   0.400000 (  0.489191)
merge:    0.920000   0.110000   1.030000 (  1.149708)
or_nil:   0.350000   0.090000   0.440000 (  0.528287)
 
With all option values passed
Rehearsal -------------------------------------------
delete:   0.420000   0.050000   0.470000 (  0.547446)
merge:    0.970000   0.130000   1.100000 (  1.228093)
or_nil:   0.420000   0.050000   0.470000 (  0.574593)
---------------------------------- total: 2.040000sec
 
              user     system      total        real
delete:   0.400000   0.050000   0.450000 (  0.485888)
merge:    0.960000   0.140000   1.100000 (  1.237509)
or_nil:   0.400000   0.060000   0.460000 (  0.536063)

Ruby 1.9.0 Results

Below are the results of running the options passing benchmark in Ruby 1.9.0:

With no option values passed
Rehearsal -------------------------------------------
delete:   0.150000   0.000000   0.150000 (  0.230391)
merge:    0.540000   0.010000   0.550000 (  0.645735)
or_nil:   0.120000   0.000000   0.120000 (  0.188468)
---------------------------------- total: 0.820000sec
 
              user     system      total        real
delete:   0.160000   0.000000   0.160000 (  0.238828)
merge:    0.520000   0.000000   0.520000 (  0.610940)
or_nil:   0.120000   0.000000   0.120000 (  0.180744)
 
With some option values passed
Rehearsal -------------------------------------------
delete:   0.220000   0.000000   0.220000 (  0.294110)
merge:    0.690000   0.010000   0.700000 (  0.803382)
or_nil:   0.220000   0.000000   0.220000 (  0.297869)
---------------------------------- total: 1.140000sec
 
              user     system      total        real
delete:   0.210000   0.000000   0.210000 (  0.283983)
merge:    0.700000   0.000000   0.700000 (  0.802766)
or_nil:   0.230000   0.000000   0.230000 (  0.295433)
 
With all option values passed
Rehearsal -------------------------------------------
delete:   0.270000   0.010000   0.280000 (  0.348052)
merge:    0.770000   0.040000   0.810000 (  0.947252)
or_nil:   0.240000   0.010000   0.250000 (  0.336790)
---------------------------------- total: 1.340000sec
 
              user     system      total        real
delete:   0.280000   0.000000   0.280000 (  0.660810)
merge:    0.750000   0.000000   0.750000 (  1.618925)
or_nil:   0.240000   0.000000   0.240000 (  0.478159)

In Ruby 1.8.6 the delete code is consistently more efficient, whereas in Ruby 1.9.0 the or_nil code is mostly more efficient, except for the With some option values passed scenario.

This once again shows that when optimizing performance on the code level, you need to be careful that you justify your micro-optimizations before you create code spaghetti just “because….”. Readability and maintainability is most important and when needed, only then should you optimize.

Charles Oliver Nutter’s Response
Charles Oliver Nutter (of JRuby) responded as well:

Stumbled on your blog post and ran the same numbers in JRuby on soylatte Java 6 on basically the same machine. My numbers for Ruby 1.8.6p111 were roughly the same as yours.

Here’s Ruby:

 user system total real
delete: 0.200000 0.000000 0.200000 ( 0.207049)
merge: 0.710000 0.010000 0.720000 ( 0.720514)
or_nil: 0.260000 0.000000 0.260000 ( 0.256742)
 
With some option values passed
 
user system total real
delete: 0.250000 0.000000 0.250000 ( 0.260502)
merge: 0.810000 0.010000 0.820000 ( 0.818434)
or_nil: 0.290000 0.000000 0.290000 ( 0.294220)
 
With all option values passed
 
user system total real
delete: 0.320000 0.000000 0.320000 ( 0.319335)
merge: 0.890000 0.010000 0.900000 ( 0.896330)
or_nil: 0.330000 0.000000 0.330000 ( 0.336673)

And here’s JRuby:

With no option values passed
 
user system total real
delete: 0.133000 0.000000 0.133000 ( 0.133000)
merge: 0.318000 0.000000 0.318000 ( 0.319000)
or_nil: 0.474000 0.000000 0.474000 ( 0.474000)
 
With some option values passed
 
user system total real
delete: 0.170000 0.000000 0.170000 ( 0.169000)
merge: 0.295000 0.000000 0.295000 ( 0.295000)
or_nil: 0.145000 0.000000 0.145000 ( 0.146000)
 
With all option values passed
 
user system total real
delete: 0.154000 0.000000 0.154000 ( 0.154000)
merge: 0.289000 0.000000 0.289000 ( 0.288000)
or_nil: 0.101000 0.000000 0.101000 ( 0.100000)

Looks like something’s artificially slowing down the “or_nil” case in the first scenario, but the rest of the numbers look pretty solid.

Interesting results all around. I’m not going to lose sleep over the milliseconds I’m saving/losing with various approaches, so I’ll likely use the ops = ops[:foo] || ‘default’ approach for readability.

Ruby: benchmarking ways to pass options to a method 2

Posted by Bryan on February 05, 2008

(reposting from my Sun blog)

I’ve wondered before what the fastest way is to pass a hash of options to a method in Ruby.. so today I benchmarked a few methods I’ve used in the past.

I’ve seen three main ways of passing an options hash to a method and extracting the options or use default values if they weren’t passed in:

  • Merge the options with a hash of defaults, then assign values to local variables (or just use the options hash directly within the method)
  • Use something like var = options[:var] || ‘default’
  • Use the delete method on the hash, e.g., var = options.delete(:var) || ‘default’

From what I can tell, using delete is the fastest:

require 'benchmark'
 
def ops_delete(ops={})
  a = ops.delete(:a) || 11
  b = ops.delete(:b) || 22
  c = ops.delete(:c) || 33
  d = ops.delete(:d) || 44
end
 
def ops_merge(ops={})
  ops = {:a => 11, :b => 22, :c => 33, :d => 44}.merge(ops)
  a = ops[:a]
  b = ops[:b]
  c = ops[:c]
  d = ops[:d]
end
 
def ops_or(ops={})
  a = ops[:a] || 11
  b = ops[:b] || 22
  c = ops[:c] || 33
  d = ops[:d] || 44
end
 
 
n = 100000
puts "With no option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete; end }
  x.report("merge:")  { n.times do ops_merge;  end }
  x.report("ops_or:") { n.times do ops_or;     end }
end
 
puts
puts "With some option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete(:a => 5, :c => 2); end }
  x.report("merge:")  { n.times do ops_merge(:a => 5, :c => 2);  end }
  x.report("ops_or:") { n.times do ops_or(:a => 5, :c => 2);     end }
end
 
puts
puts "With all option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete(:a => 5, :b => 1, :c => 2, :d => 4); end }
  x.report("merge:")  { n.times do ops_merge(:a => 5, :b => 1, :c => 2, :d => 4);  end }
  x.report("ops_or:") { n.times do ops_or(:a => 5, :b => 1, :c => 2, :d => 4);     end }
end

Results (rehearsals omitted):

With no option values passed
              user     system      total        real
delete:   0.210000   0.000000   0.210000 (  0.209877)
merge:    0.720000   0.000000   0.720000 (  0.750394)
ops_or:   0.260000   0.000000   0.260000 (  0.258416)
 
With some option values passed
              user     system      total        real
delete:   0.250000   0.000000   0.250000 (  0.255536)
merge:    0.830000   0.000000   0.830000 (  0.831358)
ops_or:   0.310000   0.000000   0.310000 (  0.314737)
 
With all option values passed
              user     system      total        real
delete:   0.310000   0.000000   0.310000 (  0.306889)
merge:    0.900000   0.000000   0.900000 (  0.905261)
ops_or:   0.340000   0.000000   0.340000 (  0.348061)

This was done in Ruby 1.8.4 on a 2GHz MacBook Intel Core Duo with 1GB 667 MHz DDR2 SDRAM.