Transactional in-memory database tests with Sequel and SQLite
Instant feedback is one of the prominent features I look for when referring to "good test code". Tests that involve a database often lack this quality. Here, I am referring to a test's start up time, rather then the actual time a test takes to execute. This needn't be the case when coding in Ruby, given the negligible lag related to firing up an MRI interpreter and the equally fast start up of in-memory SQLite.
Using an in-memory database for testing is a common technique for speeding up functional tests that hit the database. Sequel makes using SQLite in its in-memory mode particularly easy.
require 'rubygems' require 'sequel' DB = Sequel.sqlite %p Database setup code can follow this step.
DB.create_table :items do column :name, :string end
The above is for the sake of simplicity, and in a real world scenario it would involve running migrations against the application's current schema.
Another useful feature is the ability to run these tests transactionally, that is, never actually change the database state and avoid having to deal with unnecessary database clean up. As an added benefit, a relative speed bump is achieved by not performing database write operations. A simple extension to Test::Unit::TestCase will do the trick.
class Test::Unit::TestCase alias run_orig run def run(result, &block) DB.transaction do begin run_orig(result, &block) ensure rollback! end end end end
Following are some sample tests, with nothing out of the ordinary about them.
class SomeTest < Test::Unit::TestCase def test_rock items = DB[:items] items.insert(:name => 'rock') assert_equal(1, items.count) assert_equal('rock', items[1][:name]) end def test_coast_is_clear assert_equal(0, DB[:items].size) end def test_insert_ten_items items = DB[:items] 10.times { |i| items.insert(:name => "item_#{i}") } assert_equal(10, items.size) end end
These tests not only execute in milliseconds, but also largely eliminate any noticeable lag before they run.
TW-MacBook-Pro:Desktop gmalamid$ ruby some_test.rb Loaded suite some_test Started ... Finished in 0.002673 seconds. 3 tests, 4 assertions, 0 failures, 0 errors