Friday, March 31, 2017

RSpec - Quick Guide

RSpec - Introduction

RSpec is a unit test framework for the Ruby programming language. RSpec is different than traditional xUnit frameworks like JUnit because RSpec is a Behavior driven development tool. What this means is that, tests written in RSpec focus on the “behavior” of an application being tested. RSpec does not put emphasis on, how the application works but instead on how it behaves, in other words, what the application actually does.

RSpec Environment

First of all, you will need to install Ruby on your computer. However, if you haven’t already done earlier, then you can download and install Ruby from the main Ruby website − Ruby.
If you are installing Ruby on Windows, you should have the Ruby installer for Windows here at − http://www.rubyinstaller.org
For this tutorial, you will only need text editor, such as Notepad and a command line console. The examples here will use cmd.exe on Windows.
To run cmd.exe, simply click on the Start menu and type “cmd.exe”, then hit the Return key.
At the command prompt in your cmd.exe window, type the following command to see what version of Ruby you are using −
ruby -v
You should see the below output that looks similar to this −
ruby 2.2.3p173 (2015-08-18 revision 51636) [x64-mingw32]
The examples in this tutorial will use Ruby 2.2.3 but any version of Ruby higher than 2.0.0 will suffice. Next, we need to install the RSpec gem for your Ruby installation. A gem is a Ruby library which you can use in your own code. In order to install a gem, you need to use the gem command.
Let’s install the Rspec gem now. Go back to your cmd.exe Window and type the following −
gem install rspec
You should have a list of dependent gems that were installed, these are gems that the rspec gem needs to function correctly. At the end of the output, you should see something that looks like this −
Done installing documentation for diff-lcs, rspec-support, rspec-mocks,
   rspec-expectations, rspec-core, rspec after 22 seconds 
6 gems installed
Do not worry, if your output does not look exactly the same. Also, if you are using a Mac or Linux computer, you may need to either run gem install rspec command using sudo or use a tool like HomeBrew or RVM to install the rspec gem.
Hello World
To get started, let’s create a directory (folder) to store our RSpec files. In your cmd.exe window, type the following −
cd \
Then type −
mkdir rspec_tutorial
And finally, type −
cd rspec_tutorial
From here, we’re going to create another directory named spec, do that by typing −
mkdir spec
We are going to store our RSpec files in this folder. RSpec files are known as “specs”. If this seems confusing to you, you can think of a spec file as a test file. RSpec uses the term “spec” which is a short form for “specification”.
Since, RSpec is a BDD test tool, the goal is to focus on what the application does and whether or not it follows a specification. In behavior driven development, the specification is often described in terms of a “User Story”. RSpec is designed to make it clear whether the target code is behaving correctly, in other words following the specification.
Let’s return to our Hello World code. Open a text editor and add the following code −
class HelloWorld

   def say_hello 
      "Hello World!"
   end
   
end

describe HelloWorld do 
   context When testing the HelloWorld class do 
      
      it "should say 'Hello World' when we call the say_hello method" do 
         hw = HelloWorld.new 
         message = hw.say_hello 
         expect(message).to eq "Hello World!"
      end
      
   end
end
Next, save this to a file named hello_world_spec.rb in the spec folder that you created above. Now back in your cmd.exe window, run this command −
rspec spec spec\hello_world_spec.rb
When the command completes, you should see output that looks like this −
Finished in 0.002 seconds (files took 0.11101 seconds to load) 
1 example, 0 failures
Congratulations, you just created and ran your first RSpec unit test!
In the next section, we will continue to discuss the syntax of RSpec files.

RSpec - Basic Syntax

Let’s take a closer look at the code of our HelloWorld example. First of all, in case it isn’t clear, we are testing the functionality of the HelloWorld class. This of course, is a very simple class that contains only one method say_hello().
Here is the RSpec code again −
describe HelloWorld do 
   context When testing the HelloWorld class do 
      
      it "The say_hello method should return 'Hello World'" do 
         hw = HelloWorld.new 
         message = hw.say_hello 
         expect(message).to eq "Hello World!" 
      end
      
   end 
end

The describe Keyword

The word describe is an RSpec keyword. It is used to define an “Example Group”. You can think of an “Example Group” as a collection of tests. The describe keyword can take a class name and/or string argument. You also need to pass a block argument to describe, this will contain the individual tests, or as they are known in RSpec, the “Examples”. The block is just a Ruby block designated by the Ruby do/end keywords.

The context Keyword

The context keyword is similar to describe. It too can accept a class name and/or string argument. You should use a block with context as well. The idea of context is that it encloses tests of a certain type.
For example, you can specify groups of Examples with different contexts like this −
context “When passing bad parameters to the foobar() method” 
context “When passing valid parameters to the foobar() method” 
context “When testing corner cases with the foobar() method”
The context keyword is not mandatory, but it helps to add more details about the examples that it contains.

The it Keyword

The word it is another RSpec keyword which is used to define an “Example”. An example is basically a test or a test case. Again, like describe and context, it accepts both class name and string arguments and should be used with a block argument, designated with do/end. In the case of it, it is customary to only pass a string and block argument. The string argument often uses the word “should” and is meant to describe what specific behavior should happen inside the it block. In other words, it describes that expected outcome is for the Example.
Note the it block from our HelloWorld Example −
it "The say_hello method should return 'Hello World'" do
The string makes it clear what should happen when we call say hello on an instance of the HelloWorld class. This part of the RSpec philosophy, an Example is not just a test, it’s also a specification (a spec). In other words, an Example both documents and tests the expected behavior of your Ruby code.

The expect Keyword

The expect keyword is used to define an “Expectation” in RSpec. This is a verification step where we check, that a specific expected condition has been met.
From our HelloWorld Example, we have −
expect(message).to eql "Hello World!"
The idea with expect statements is that they read like normal English. You can say this aloud as “Expect the variable message to equal the string ‘Hello World’”. The idea is that its descriptive and also easy to read, even for non-technical stakeholders such as project managers.
The to keyword
The to keyword is used as part of expect statements. Note that you can also use the not_to keyword to express the opposite, when you want the Expectation to be false. You can see that to is used with a dot, expect(message).to, because it actually just a regular Ruby method. In fact, all of the RSpec keywords are really just Ruby methods.
The eql keyword
The eql keyword is a special RSpec keyword called a Matcher. You use Matchers to specify what type of condition you are testing to be true (or false).
In our HelloWorld expect statement, it is clear that eql means string equality. Note that, there are different types of equality operators in Ruby and consequently different corresponding Matchers in RSpec. We will explore the many different types of Matchers in a later section.

RSpec - Writing Specs

In this chapter, we will create a new Ruby class, save it in its own file and create a separate spec file to test this class.
First, in our new class, it is called StringAnalyzer. It’s a simple class that, you guessed it, analyzes strings. Our class has only one method has_vowels? which as its names suggests, returns true if a string contains vowels and false if it doesn’t. Here’s the implementation for StringAnalyzer
class StringAnalyzer 
   def has_vowels?(str) 
      !!(str =~ /[aeio]+/i) 
   end 
end
If you followed the HelloWorld section, you created a folder called C:\rspec_tutorial\spec.
Delete the hello_world.rb file if you have it and save the StringAnalyzer code above to a file called string_analyzer.rb in the C:\rspec_tutorial\spec folder.
Here is the source for our spec file to test StringAnalyzer −
require 'string_analyzer' 

describe StringAnalyzer do 
   context "With valid input" do 
      
      it "should detect when a string contains vowels" do 
         sa = StringAnalyzer.new 
         test_string = 'uuu' 
         expect(sa.has_vowels? test_string).to be true 
      end 
  
      it "should detect when a string doesn't contain vowels" do 
         sa = StringAnalyzer.new 
         test_string = 'bcdfg' 
         expect(sa.has_vowels? test_string).to be false
      end 
      
   end 
end
Save this in the same spec directory, giving it the name string_analyzer_test.rb.
In your cmd.exe window, cd to the C:\rspec_tutorial folder and run this command: dir spec
You should see the following −
Directory of C:\rspec_tutorial\spec
09/13/2015 08:22 AM  <DIR>    .
09/13/2015 08:22 AM  <DIR>    ..
09/12/2015 11:44 PM                 81 string_analyzer.rb
09/12/2015 11:46 PM              451 string_analyzer_test.rb
Now we’re going to run our tests, run this command: rspec spec
When you pass the name of a folder to rspec, it runs all of the spec files inside of the folder. You should see this result −
No examples found.

Finished in 0 seconds (files took 0.068 seconds to load)
0 examples, 0 failures
The reason that this happened is that, by default, rspec only runs files whose names end in “_spec.rb”. Rename string_analyzer_test.rb to string_analyzer_spec.rb. You can do that easily by running this command −
ren spec\string_analyzer_test.rb string_analyzer_spec.rb
Now, run rspec spec again, you should see output that looks like this −
F.
Failures:

   1) StringAnalyzer With valid input should detect when a string contains vowels
      Failure/Error: expect(sa.has_vowels? test_string).to be true 
         expected true
            got false
      # ./spec/string_analyzer_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.015 seconds (files took 0.12201 seconds to load)
2 examples, 1 failure

Failed examples:
rspec ./spec/string_analyzer_spec.rb:6 # StringAnalyzer With valid 
   input should detect when a string contains vowels
Do you see what just happened? Our spec failed because we have a bug in 
   StringAnalyzer. The bug is simple to fix, open up string_analyzer.rb 
   in a text editor and change this line:
!!(str =~ /[aeio]+/i)
to this:
!!(str =~ /[aeiou]+/i)
Now, save the changes you just made in string_analyizer.rb and run the rspec spec command again, you should now see output that looks like −
..
Finished in 0.002 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures
Congratulations, the examples (tests) in your spec file are now passing. We fixed a bug in the regular expression which has vowels method but our tests are far from complete.
It would make sense to add more examples that tests various types of input strings with the has vowels method.
The following table shows some of the permutations that could be added in new Examples (it blocks)
Input string Description Expected result with has_vowels?
‘aaa’, ‘eee’, ‘iii’, ‘o’ Only one vowel and no other letters. true
‘abcefg’ ‘At least one vowel and some consonants’ true
‘mnklp’ Only consonants. false
‘’ Empty string (no letters) false
‘abcde55345&??’ Vowels, consonants, numbers and punctuation characters. true
‘423432%%%^&’ Numbers and punctuation characters only. false
‘AEIOU’ Upper case vowels only. true
‘AeiOuuuA’ Upper case and lower vowels only. true
‘AbCdEfghI’ Upper and lower case vowels and consonants. true
‘BCDFG’ Upper case consonants only. false
‘ ‘ Whitespace characters only. false
It is up to you to decide, which examples to add to your spec file. There are many conditions to test for, you need to determine what subset of conditions is most important and tests your code the best.
The rspec command offers many different options, to see them all, type rspec -help. The following table lists the most popular options and describes what they do.
Option/flag Description
-I PATH Adds PATH to the load (require) path that rspec uses when looking for Ruby source files.
-r, --require PATH Adds a specific source file to be required in your spec. file(s).
--fail-fast With this option, rspec will stop running specs after the first Example fails. By default, rspec runs all specified spec files, no matter how many failures there are.
-f, --format FORMATTER This option allows you to specify different output formats. See the section on Formatters for more details about output formats.
-o, --out FILE This option directs rspec to write the test results to the output file FILE instead of to standard out.
-c, --color Enables color in rspec’s output. Successful Example results will display in green text, failures will print in red text.
-b, --backtrace Displays full error backtraces in rspec’s output.
-w, --warnings Displays Ruby warnings in rspec’s output.
-P, --pattern PATTERN Load and run spec files that match the pattern PATTERN. For example, if you pass -p “*.rb”, rspec will run all Ruby files, not just the ones that end in “_spec.rb”
-e, --example STRING This option directs rspec to run all Examples that contain the text STRING in their descriptions.
-t, --tag TAG With this option, rspec will only run examples that contain the tag TAG. Note that TAG is specified as a Ruby symbol. See the section on RSpec Tags for more details.

RSpec - Matchers

If you recall our original Hello World example, it contained a line that looked like this −
expect(message).to eq "Hello World!"
The keyword eql is an RSpec “matcher”. Here, we will introduce the other types of matchers in RSpec.

Equality/Identity Matchers

Matchers to test for object or value equality.
Matcher Description Example
eq Passes when actual == expected expect(actual).to eq expected
eql Passes when actual.eql?(expected) expect(actual).to eql expected
be Passes when actual.equal?(expected) expect(actual).to be expected
equal Also passes when actual.equal?(expected) expect(actual).to equal expected

Example

describe "An example of the equality Matchers" do 

   it "should show how the equality Matchers work" do 
      a = "test string" 
      b = a 
      
      # The following Expectations will all pass 
      expect(a).to eq "test string" 
      expect(a).to eql "test string" 
      expect(a).to be b 
      expect(a).to equal b 
   end
   
end
When the above code is executed, it will produce the following output. The number of seconds may be slightly different on your computer −
.
Finished in 0.036 seconds (files took 0.11901 seconds to load)
1 example, 0 failures

Comparison Matchers

Matchers for comparing to values.
Matcher Description Example
> Passes when actual > expected expect(actual).to be > expected
>= Passes when actual >= expected expect(actual).to be >= expected
< Passes when actual < expected expect(actual).to be < expected
<= Passes when actual <= expected expect(actual).to be <= expected
be_between inclusive Passes when actual is <= min and >= max expect(actual).to be_between(min, max).inclusive
be_between exclusive Passes when actual is < min and > max expect(actual).to be_between(min, max).exclusive
match Passes when actual matches a regular expression expect(actual).to match(/regex/)

Example

describe "An example of the comparison Matchers" do

   it "should show how the comparison Matchers work" do
      a = 1
      b = 2
      c = 3  
      d = 'test string'
      
      # The following Expectations will all pass
      expect(b).to be > a
      expect(a).to be >= a 
      expect(a).to be < b 
      expect(b).to be <= b 
      expect(c).to be_between(1,3).inclusive 
      expect(b).to be_between(1,3).exclusive 
      expect(d).to match /TEST/i 
   end
   
end
When the above code is executed, it will produce the following output. The number of seconds may be slightly different on your computer −
. 
Finished in 0.013 seconds (files took 0.11801 seconds to load) 
1 example, 0 failures

Class/Type Matchers

Matchers for testing the type or class of objects.
Matcher Description Example
be_instance_of Passes when actual is an instance of the expected class. expect(actual).to be_instance_of(Expected)
be_kind_of Passes when actual is an instance of the expected class or any of its parent classes. expect(actual).to be_kind_of(Expected)
respond_to Passes when actual responds to the specified method. expect(actual).to respond_to(expected)

Example

describe "An example of the type/class Matchers" do
 
   it "should show how the type/class Matchers work" do
      x = 1 
      y = 3.14 
      z = 'test string' 
      
      # The following Expectations will all pass
      expect(x).to be_instance_of Fixnum 
      expect(y).to be_kind_of Numeric 
      expect(z).to respond_to(:length) 
   end
   
end
When the above code is executed, it will produce the following output. The number of seconds may be slightly different on your computer −
. 
Finished in 0.002 seconds (files took 0.12201 seconds to load) 
1 example, 0 failures

No comments:

Post a Comment