Yesterday, I read Brian Cardarella‘s post entitled Brian Cardarella’s post entitled A case against Mocking and Stubbing. In the article, Brian says:

SQLite3 can be an in-memory database. Problem solved, right? Not quite. SQLite3 is pretty limited. Most people are probably using MySQL and rely upon many of the SQL functions that are included.

What would be nice (and well beyond my ability) is to have a Gem that simulated the database you use, only it is in-memory. Optimized for small data sets. No need to go through a heavy hashing algorithm. Keep it light. Keep it fast.

Brian Cardarella in A case against Mocking and Stubbing

MySQL already has the MEMORY storage engine, and I wanted to see if that would help for testing purposes. Since we’re staying in MySQL-land, this should have been a simple matter.

First, the good news. I had to change only a couple of lines:

 1 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/vendor/rails/activerecor
 2 index 1e452ae..c207080 100644
 3 --- a/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
 4 +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
 5 @@ -442,7 +442,13 @@ module ActiveRecord
 6        end
 7  
 8        def create_table(table_name, options = {}) #:nodoc:
 9 -        super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
10 +        engine = case Rails.env
11 +        when "test"
12 +          "MEMORY"
13 +        else
14 +          "InnoDB"
15 +        end
16 +        super(table_name, options.reverse_merge(:options => "ENGINE=#{engine}"))
17        end
18  
19        def rename_table(table_name, new_name)
20 diff --git a/vendor/rails/railties/lib/tasks/databases.rake b/vendor/rails/railties/lib/tasks/databases.rake
21 index 5cb27f1..c520d4a 100644
22 --- a/vendor/rails/railties/lib/tasks/databases.rake
23 +++ b/vendor/rails/railties/lib/tasks/databases.rake
24 @@ -368,9 +368,9 @@ namespace :db do
25  
26      desc 'Check for pending migrations and load the test schema'
27      task :prepare => 'db:abort_if_pending_migrations' do
28 -      if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
29 -        Rake::Task[{ :sql  => "db:test:clone_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]].i
30 -      end
31 +      # if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
32 +      #   Rake::Task[{ :sql  => "db:test:clone_structure", :ruby => "db:test:load" }[ActiveRecord::Base.schema_format]]
33 +      # end
34      end
35    end
36  

Great, but there’s little benefit. First, a regular run (InnoDB):

 1 $ time rake
 2 (in /Users/francois/Documents/work/fasttest)
 3 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/password_reset_mailer_test.rb" "test/unit/person_test.rb" 
 4 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
 5 Started
 6 ............................
 7 Finished in 0.520087 seconds.
 8 
 9 28 tests, 33 assertions, 0 failures, 0 errors
10 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/functional/accounts_controller_test.rb" "test/functional/password_resets_controller_test.rb" "test/functional/people_controller_test.rb" "test/functional/sessions_controller_test.rb" 
11 [DEPRECATION] should_be_restful is deprecated.  Please see http://thoughtbot.lighthouseapp.com/projects/5807/tickets/78 for more information.
12 [DEPRECATION] should_be_restful is deprecated.  Please see http://thoughtbot.lighthouseapp.com/projects/5807/tickets/78 for more information.
13 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
14 Started
15 .....................................................
16 Finished in 1.828175 seconds.
17 
18 53 tests, 72 assertions, 0 failures, 0 errors
19 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb"  
20 
21 real  0m10.365s
22 user  0m6.582s
23 sys  0m1.925s

Next, a run with the MEMORY engine:

 1 $ time rake
 2 (in /Users/francois/Documents/work/fasttest)
 3 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/unit/password_reset_mailer_test.rb" "test/unit/person_test.rb" 
 4 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
 5 Started
 6 ............................
 7 Finished in 0.602607 seconds.
 8 
 9 28 tests, 33 assertions, 0 failures, 0 errors
10 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb" "test/functional/accounts_controller_test.rb" "test/functional/password_resets_controller_test.rb" "test/functional/people_controller_test.rb" "test/functional/sessions_controller_test.rb" 
11 [DEPRECATION] should_be_restful is deprecated.  Please see http://thoughtbot.lighthouseapp.com/projects/5807/tickets/78 for more information.
12 [DEPRECATION] should_be_restful is deprecated.  Please see http://thoughtbot.lighthouseapp.com/projects/5807/tickets/78 for more information.
13 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader
14 Started
15 .....................................................
16 Finished in 1.132142 seconds.
17 
18 53 tests, 72 assertions, 0 failures, 0 errors
19 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.3/lib/rake/rake_test_loader.rb"  
20 
21 real  0m9.111s
22 user  0m6.862s
23 sys  0m1.870s

Note that in test/test_helper.rb, I had to disable transactional fixtures. This would account for a lot the lost time difference.

If you want to play with this further, the sample application’s code is available at http://github.com/francois/fasttest

I would be interested in seeing other people’s runs, to know if it’s just my machine that runs at essentially the same speed.

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on