Testing DAO

16 views
Skip to first unread message

aguf...@gmail.com

unread,
May 18, 2015, 1:15:30 PM5/18/15
to rs...@googlegroups.com
I'm working in a project which involves ruby, sequel and sinatra. I read about which testing framework to use, and RSpec seems to be the most used by the community.

The project consists in a CRUD application, using DAO as the persistence pattern.
    
    require 'sequel'

    DB = Sequel.sqlite
    DB.create_table :foos do
      primary_key :id
      int :foo_attribute1
      int :foo_attribute2
    end

    class Foo
      attr_accessor :id, :foo_attribute1, :foo_attribute2
    end

    module FooDAO
      extend self

      def save(f)
        DB[:foos].insert(foo_attribute1: f.foo_attribute1, foo_attribute2: f.foo_attribute2)
      end

      def [](id)
        DB[:foos].where(id: id).first
      end

      def update(f)
        DB[:foos].where(id: f.id).update(foo_attribute1: f.foo_attribute1, foo_attribute2: f.foo_attribute2)
      end

      def count
        DB[:foos].count
      end
    end

    describe FooDAO do
      context 'save' do
        f = Foo.new
        f.foo_attribute1 = 1
        f.foo_attribute2 = 2
        FooDAO.save f
        it { expect(FooDAO.count).to eq 1 }
      end

      context 'get' do
        it { expect(FooDAO[1][:foo_attribute1]).to eq 1 }
        it { expect(FooDAO[1][:foo_attribute2]).to eq 2 }
      end

      context 'update' do
        f = Foo.new
        f.id = 1
        f.foo_attribute1 = 2
        f.foo_attribute2 = 3
        FooDAO.update f
        it { expect(FooDAO[1][:foo_attribute1]).to eq 2 }
        it { expect(FooDAO[1][:foo_attribute2]).to eq 3 }
      end
    end

In this case, the context `get` won't pass the examples. But if I comment the context `update`, they will.

Antonio Antillon

unread,
May 18, 2015, 1:27:04 PM5/18/15
to rs...@googlegroups.com
I think is because you have not created any FooDAO records in the get context.

Database state is not shared between contexts (unless they're nested inside a top-level context which created records).

--
You received this message because you are subscribed to the Google Groups "rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rspec+un...@googlegroups.com.
To post to this group, send email to rs...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/46177435-e16e-4c9a-a3e8-9bcd017a777c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Myron Marston

unread,
May 18, 2015, 2:46:49 PM5/18/15
to rs...@googlegroups.com, aguf...@gmail.com

The problem is that your tests are not written to be independent. It’s really, really important that each test is able to pass when run individually and also pass when all the specs are run, in any order. RSpec supports random ordering (--order random) to help surface cases where you’ve failed to do this. I recommend you use it.

In your specific case, a big part of the problem is that you’re creating and mutating the DB records directly in your context blocks. context blocks are not designed for this. They provide a way to group multiple examples, provide helper methods, perform common setup via a before hook, etc, but you don’t want to directly manipulate the objects you are testing in a context block — instead move that into a before hook or helper method. When RSpec runs, it loads your spec file and evaluates all your describe and context blocks but does not run the examples (the it blocks), the hooks, or any let declarations. Instead, it stores them for later use. After loading all the spec files, RSpec applies any filtering, ordering, etc to the examples, and then runs them. That means that in your case, this is what’s happening:

  1. The ‘save’ context is evaluated, causing a record to be saved with particular attributes.
  2. The example in the ‘save` context is defined but not executed.
  3. The ‘get’ context is evaluated, which causes the two examples there to be defined but not executed.
  4. The ‘update’ context is evaluated, causing the earlier saved record to be updated. Two examples are also defined but not executed.
  5. Now that RSpec has finished loading all the specs, it begins running them. It starts with the example in the ‘save’ context, then the examples in the ‘get’ context, then the examples in the ‘update’ context.
  6. Since the ‘update’ context had earlier updated the record, the examples in the ‘get’ context fail.

To avoid these problems, here’s what I suggest:

  • Move your record manipulation logic out of the context blocks and into a before hook defined in those contexts. This ensures that the logic will run just before the examples execute that depend on them, regardless of what order the examples execute in.
  • To ensure independence of your specs, you need each spec to work off of a clean DB slate. The easiest way to achieve this is to wrap each example in a DB transaction, so that any changes made in one example are rolled back when it completes. The sequel docs have an RSpec code snippet that shows how to do this.
  • I recommend adding --order random to .rspec so that your specs run in random order. This will surface ordering dependencies to you so that you can quickly fix them at the time you create them, rather than having them sit dormant in your test suite only to cause problems later. Each time RSpec runs it will print the seed it used for that run’s randomization, and you can use --seed <seed> to reproduce the run.

HTH,
Myron

Reply all
Reply to author
Forward
0 new messages