RSpec is a behavior-driven testing tool for the Ruby language. RSpec is used in test driven development (“TDD”), where the developer writes the test based on what s/he hopes the program will accomplish, and then creates the program to accomplish the goals of the test.
While this would seem to create additional work for the developer, it actually saves time in the development process as the developer is more easily able to tell whether the program is functioning properly.
Installing RSpec
We’ll start by making sure you have rspec installed. If you don’t, you can easily install rspec with gem install rspec.
Setting Up Our Directory and Test Suite
To begin, we’ll create a directory called school using mkdir school. We’ll then migrate to our school directory and create a file called student.rb using touch student.rb at the command line.
It’s now time for us to create our test suite. To do this, we’ll create a directory inside the school directory called spec by running mkdir spec. Inside the spec directory, we’ll create two files: student_spec.rb and spec_helper.rb using touch student_spec.rb and touch spec_helper.rb.
There should be one spec file per class. However, even with multiple classes, we only need one spec_helper, as the spec_helper exists to require the Ruby classes we’re testing.
spec_helper.rb
Inside our spec_helper file, we need to require our Ruby classes:
require_relative ‘../student’
require_relative is used to require a directory or file relative to the current directory.
student_spec.rb
In student_spec, we’ll create the tests needed to ensure our Ruby code in student.rb is running properly. The first thing we’ll want to do is require the spec_helper with require_relative ‘spec_helper.rb’. We’ll then want to create a shell for our specs. Our file thus far will look something like:
require_relative 'spec_helper.rb' describe Student do # tests end
describe simply describes what we’re testing which, in this case, is the class Student.
Ideally, we’d like a program that will keep track of all students at our school. To do this, we want our program to be able to initialize a student, save the student’s name so it cannot be changed, and be able to list all of the students. To do this, our code will look something like:
require_relative 'spec_helper.rb' describe Student do before(:each) do Student.reset_all end let(:student) { Student.new("Koren") } it "can initialize a student" do expect(student).to be_a(Student) end it "initializes with a name" do expect(student.name).to eq("Koren") end it "can't change its name" do expect { student.name = "Koren" }.to raise_error end it "can count how many students have been created" do Student.new("student") expect(Student.count).to eq(1) end end
Let’s break down the above code: We are describing the student class, and the things we’d like the class to do. Each time we run rspec, we want our class to reset itself with a method called reset_all. This is because each time we run rspec we will create a new instance of the class, and our tests will be compromised.
After the reset, we want our class to accomplish 4 things: (1) it can initialize a student, (2) it can initialize a student with a name, (3) it should not allow a student’s name to be changed, and (4) it should keep track of how many students have been initialized. Of course, due to the reset, this will always be 1 when we run rspec, though this just ensures the program is functioning properly. In reality, there will likely be many more instances of the student class.
Running RSpec
When we first run rspec, we are unable to see our examples and errors. Instead we get the following error:
/Users/korenlcohen/Flatiron/school/spec/students_spec.rb:3:in `<top (required)>': uninitialized constant Student (NameError) from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/configuration.rb:1057:in `load' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/configuration.rb:1057:in `block in load_spec_files' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/configuration.rb:1057:in `each' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/configuration.rb:1057:in `load_spec_files' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/runner.rb:97:in `setup' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/runner.rb:85:in `run' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/runner.rb:70:in `run' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/lib/rspec/core/runner.rb:38:in `invoke' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/gems/rspec-core-3.0.2/exe/rspec:4:in `<top (required)>' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/bin/rspec:23:in `load' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/bin/rspec:23:in `<main>' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/bin/ruby_executable_hooks:15:in `eval' from /Users/korenlcohen/.rvm/gems/ruby-2.1.2/bin/ruby_executable_hooks:15:in `<main>'
As you can see, this error says we have an uninitialized constant Student: /Users/korenlcohen/Flatiron/school/spec/students_spec.rb:3:in `<top (required)>’: uninitialized constant Student (NameError) . To correct this error, we simply need to create a student class in student.rb:
class Student end
Once our class has been created, rspec will run properly:
Failures: 1) Student can initialize a student Failure/Error: Student.reset_all NoMethodError: undefined method `reset_all' for Student:Class # ./spec/students_spec.rb:6:in `block (2 levels) in <top (required)>' 2) Student initializes with a name Failure/Error: Student.reset_all NoMethodError: undefined method `reset_all' for Student:Class # ./spec/students_spec.rb:6:in `block (2 levels) in <top (required)>' 3) Student can't change its name Failure/Error: Student.reset_all NoMethodError: undefined method `reset_all' for Student:Class # ./spec/students_spec.rb:6:in `block (2 levels) in <top (required)>' 4) Student can count how many students have been created Failure/Error: Student.reset_all NoMethodError: undefined method `reset_all' for Student:Class # ./spec/students_spec.rb:6:in `block (2 levels) in <top (required)>' Finished in 0.00104 seconds (files took 0.15203 seconds to load) 4 examples, 4 failures Failed examples: rspec ./spec/students_spec.rb:11 # Student can initialize a student rspec ./spec/students_spec.rb:15 # Student initializes with a name rspec ./spec/students_spec.rb:19 # Student can't change its name rspec ./spec/students_spec.rb:23 # Student can count how many students have been
As you can see, we have 4 examples and 4 failures. Assuming you know simple Ruby, you’ll be able to make these tests pass quickly with the following code:
class Student attr_reader :name @@students = [] def initialize(name) @name = name @@students << self end def self.count @@students.count end def self.reset_all @@students.clear end end
If you run the test now, we’ll have 0 errors. Of course, as your programs become more complicated and must accomplish a greater number of tasks, your test suite will become comparably more complicated.
RSpec Tips
When you’re working with a test suite that contains multiple examples, sometimes it helps to streamline the process by using rspec –fail-fast. This will generate a single failure at a time, so you can easily zero in on an error instead of scrolling through multiple failures at the same time.
If you want your examples and failures to appear in color, you can use rspec -c.
- PM Career Story - April 28, 2022
- How to Transition into Product Management - December 26, 2017
- What I’ve Learned in My First Few Months as a Product Manager - October 14, 2015
Comments