Skip to content

Instantly share code, notes, and snippets.

@fknappe
Created February 28, 2012 21:47
Show Gist options
  • Save fknappe/1935360 to your computer and use it in GitHub Desktop.
Save fknappe/1935360 to your computer and use it in GitHub Desktop.
Rspec Intro

RSpec Intro

RSpec is a testing framework widely used along Ruby projects. Its paradigm is contained within the Behavior Driven Development (BDD) context, so it can be used to define internals and externals aspects of Ruby web applications. However, most developers see its major use for unit testing within the BDD context.

##Philosophy

  • Clarity X Cleverness

Always regard clarity over cleverness in your specs. This approach is based on the fact that, when you practice Test-Driven, you should make clear what are the intentions of the code that you are writing not just for you, but for others that are collaborating on the same project. Besides, a clear spec contributes as a documentation of the internals aspects of the project.

  • Matchers

RSpec offers a wide range of built in matchers to be used within specs for test expectations (for unit testing framework users, assertions) that contributes with the clarity aspect of RSpec too. Essentially, this aspect of RSpec philosophy derives the fact that RSpec actually is built under a Domain Specific Language (DSL) that is intented to be human readable.

  • Mocking

Like many others BDD-like test frameworks, RSPec offers its own mock framework. And I specially think its a very good one. Within the BDD cycle, mocks and stubs are very important to driven developers through the code behavior verification and how the application design is built. Especially mocks give developers a hand during the Test-Driven process and shows how the code under test is related to other parts (interfaces) of the application. This approach also give developers the perception of OOD aspects, like low cohesion or high coupling.

Other important benefit of mocking is the unit isolation. Like said before, RSpec is widely used for unit testing, a major part of a application test suite. Therefore, only the test suite performance (speed) perspective already justify their usage. But the mocking practice are also very helpful for those cases where the context of the code under test are related to nondeterministic behavior (like responses from external systems) or to be used to discover new collaborators.

##Best Practices

######Abuse on usage of describes and self-describing examples

Always intent to make your specs as close as possible to BDD philosophy. Use nested describes and contexts within your specs associated with self-describing examples. This approach makes developers understand what are the real responsabilities of the code under test.

Example:

describe User do
  describe "validations" do
    it { should validate_presence_of :role }
    it { should validate_presence_of :email }
    it { should validate_inclusion_of :role, :messages => User::ROLES }
  end

  describe "#able_to_act_as?" do
    context "when user has guest role" do
      subject { User.new(:role => 'guest') }
      it { should be_able_to_act_as('guest') }
      it { should_not be_able_to_act_as('user') }
      it { should_not be_able_to_act_as('admin') }
    end

    context "when user has user role" do
      subject { User.new(:role => 'user') }
      it { should be_able_to_act_as('guest')  }
      it { should be_able_to_act_as('user') }
      it { should_not be_able_to_act_as('admin') }
    end

    context "when user has admin role" do
      subject { User.new(:role => 'admin') }
      it { should be_able_to_act_as('guest') }
      it { should be_able_to_act_as('user') }
      it { should be_able_to_act_as('admin') }
    end
  end

  describe "#contact" do
    context "when user already have defined a contact" do
      before :each do
        @telephone = Utilities::Telephone.new(:number => '(111)1234-5678')
        subject.telephones.push(@telephone)
      end

      it "should get the user telephone number as its contact" do
        subject.contact.should === @telephone
      end
    end

    context "when user contact was not defined explicitly" do
      before :each do
        subject.email = '[email protected]'
      end

      it "should get the user email as its contact" do
        subject.contact.should == '[email protected]'
      end
    end
  end
end

Things to note about the snippet code above:

  • All the practices defended along this topic was show on this snippet code. See how understandable and readable was the User spec with the usage of this resources.
  • Contexts are aliases for describes within the RSpec sintax. There is a place for each one within your specs.
  • Observe how describes and contexts isolates the code under test from the rest. You can make it better, filtering your examples.
  • Respect the xUnit phases (setup, exercise, verify, teardown) and effort your spec examples for doing expectations (verifies) only (the teardown aspect is supressed by RSpec). With this approach, it is possible to maintain your specs more Ruby-like and more documentation-like (using expectations with matchers and within keys).
  • When test exercise phase is simple, you can put it within a subject and make your examples verification like described above.
  • For spec examples that not fit within de cases described above, make yourself understandable with your specs examples descriptions.
  • Always prefer matchers instead os self-declaring examples. There is a lot of matchers around there for many proposes. Or maybe create your own custom matchers? The important is: Be DRY ;-)

######Always use --format documentation while developing

When developers are practicing BDD is always a good practice to check the specs in documentation format. With this approach, it is possible to check if the feature requirements are aligned with the stakeholders expectations and if is not, what are the misunderstood part.

Example:

User
  validations
    should require role to be set
    should require email to be set
    should ensure inclusion of role in []
  #able_to_act_as?
    when user has guest role
      should be able to act as "guest"
      should not be able to act as "user"
      should not be able to act as "admin"
    when user has user role
      should be able to act as "guest"
      should be able to act as "user"
      should not be able to act as "admin"
    when user has admin role
      should be able to act as "guest"
      should be able to act as "user"
      should be able to act as "admin"
  #contact
    when user already have defined a contact
      should get the user telephone number as its contact
    when user contact was not defined explicitly
      should get the user email as its contact

Finished in 0.16521 seconds
14 examples, 0 failures

Things to note about the snippet code above:

  • This approach is very helpful when aligned with the topic above. As Dan North describes on his Introducing BDD article, an expressive test name is helpful when a test fails, because is possible to look at the test method name and identify the intended behaviour of the code
  • It's always a good idea to run the rspec documentation command before start to implement changes on some part of the project you don't know. After that, you could try to use some automation tools (like auto-test) to run the examples for you after you make some changes.
  • It's a good combination too if you try to use rspec documentation format associated with the Test-Driven ideas. Try to define your examples as pending examples and try to figured out if that is the really intention of the code under test. Remember: BDD is about making small steps (one requirement after another).

######Use Test-Helpers

######Pay attention to what your specs are trying to tell you

######Mocks and stubs

Like said before, mocks and stubs are a big part of RSpec. For this reason, the RSpec built-in mock framework

##References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment