Testing Your Ruby Code With Guard, RSpec & Pry

My recent work has been on a cloud based Ruby project for the BBC News, upcoming 2014 elections. It requires fast I/O, scalability and needs to be well tested. The "be well tested" requirement, is what I want to focus on in this tutorial.

Introduction

This project utilizes a few different Amazon services such as:

  • SQS (Simple Queue Service)
  • DynamoDB (key/value store)
  • S3 (Simple Storage Service)

We need to be able to write tests that are fast and give us instant feedback on problems with our code.

Although, we won't be using Amazon services in this tutorial, I mention them because for us to have tests that are fast, it requires us to fake these external objects (for example, we shouldn't need a network connection to run our tests, because that dependency can result in slow running tests).

Alongside tech lead Robert Kenny (who is very well versed in writing TDD (test-driven development) based Ruby applications) we've been utilizing different tools that have made this process and our programming work a lot easier.

I want to share some information about these tools with you.

The tools I'll be covering are:

  • RSpec (testing framework)
  • Guard (task runner)
  • Pry (REPL and debugging)

What Do I Need to Know Upfront?

I'm going to make an assumption that you are familiar with Ruby code and the Ruby eco-system. For example, I shouldn't need to explain to you what 'gems' are or how certain Ruby syntax/concepts work.

If you're unsure, then before you move, on I would recommend reading one of my other posts on Ruby to get yourself up to speed.

Guard

You might not be familiar with Guard, but in essence it's a command line tool that utilizes Ruby to handle different events.

For example, Guard can notify you whenever specific files have been edited and you can carry out some action based on the type of file or event that was fired.

This is known as a 'task runner', you may have heard the phrase before, as they are getting a lot of usage in the front-end/client-side world at the moment (Grunt and Gulp are two popular examples).

The reason we'll be using Guard is because it helps make the feedback loop (when doing TDD) a lot tighter. It allows us to edit our test files, see a failing test, update and save our code and immediately see if it passes or fails (depending on what we wrote).

You could use something like Grunt or Gulp instead, but we prefer to use those types of task runners for handling front-end/client-side stuff. For back-end/server-side code, we use Rake and Guard.

RSpec

RSpec, if you weren't already aware, is a testing tool for the Ruby programming language.

You run your tests (using RSpec) via the command line and I'll demonstrate how you can make this process easier via the use of Ruby's build program, Rake.

Pry

Lastly, we'll be using another Ruby gem called Pry which is an extremely powerful Ruby debugging tool which injects itself into your application, while it is running, to allow you to inspect your code and figure out why something isn't working.

TDD (Test-Driven Development)

Although not necessary for demonstrating the use of RSpec and Guard, it's worth noting that I fully endorse the use of TDD as a means to ensure every line of code you write has a purpose and has been designed in a testable and reliable manner.

I'll be detailing how we would do TDD with a simple application, so at least you'll get a feel for how the process works.

Creating an Example Project

I've created a basic example on GitHub to save you from having to type everything out yourself. Feel free to download the code

Let's now go ahead and review this project, step-by-step.

Primary Files

There are three primary files required for our example application to work, these are:

  1. Gemfile
  2. Guardfile
  3. Rakefile

We'll go over the content of each file shortly, but the first thing we need to do is get our directory structure in place.

Directory Structure

For our example project, we'll need two folders created:

  • lib (this will hold our application code)
  • spec (this will hold our test code)

This isn't a requirement for your application, you can easily tweak the code within our other files to work with whatever structure suits you.

Installation

Open up your terminal and run the following command:

Bundler is a tool that makes installing other gems easier.

Once you've run that command, create the above three files (Gemfile, Guardfile and Rakefile).

Gemfile

The Gemfile is responsible for defining a list of dependencies for our application.

Here is what it looks like:

Once this file is saved, run the command bundle install.

This will install all of our gems for us (including those gems specified within the development group).

The purpose of the development group (which is a bundler specific feature) is so when you deploy your application, you can tell your production environment to install only the gems that are required for your application to function properly.

So for example, all the gems inside the development group, aren't required for the application to function properly. They are used solely to aid us while we're developing and testing our code.

To install the appropriate gems on your production server, you would need to run something like:

Rakefile

The Rakefile will allow us to run our RSpec tests from the command line.

Here is what it looks like:

Note: you don't need Guard to be able to run your RSpec tests. We use Guard to make it easier to do TDD.

When you install RSpec, it gives you access to a built in Rake task and that's what we're using here.

We create a new instance of RakeTask which by default creates a task called spec that will look for a folder called spec and will run all the test files within that folder, using the configuration options we've defined.

In this instance, we want our shell output to have color and we want to format the output to the doc style (you could change the format to be nested as an example).

You can configure the Rake task to work any way you want and to look into different directories, if that's what you have. But the default settings work great for our application and so that's what we'll be using.

Now if I want to run the tests in my example GitHub repository, then I need to open up my terminal and run the command:

This gives us the following output:

As you can see there are zero failures. That's because although we have no application code written, we also don't have any test code written yet either.

Guardfile

The contents of this file tells Guard what to do when we run the guard command:

You'll have noticed within our Gemfile we specified the gem: guard-rspec. We need that gem to allow Guard to understand how to handle changes to RSpec related files.

If we look again at the content, we can see that if we ran guard rspec then Guard would watch the specified files and execute the specified commands once any changes to those files had occurred.

Note: because we only have one guard task, rspec, then that is run by default if we ran the command guard.

You can see Guard provides us with a watch function which we pass a Regular Expression to allow us to define what files we're interested in Guard watching.

In this instance, we're telling Guard to watch all the files within our lib and spec folders and if any changes occur to any of those files then to execute the test files within our spec folder to make sure no changes we made broke our tests (and subsequently didn't break our code).

If you have all the files downloaded from the GitHub repo then you can try out the command for yourself.

Run guard and then save one of the files to see it run the tests.

Test Code

Before we start looking at some test and application code, let me explain what our application is going to do. Our application is a single class that will return a greeting message to whoever is running the code.

Our requirements are purposely simplified, as it'll make the process we're about to undertake easier to understand.

Let's now take a look at an example specification (for example, our test file) which will describe our requirements. After that, we'll start to step through the code defined in the specification and see how we can use TDD to aid us in writing our application.

Our First Test

We're going to create a file titled example_spec.rb. The purpose of this file is to become our specification file (in other words, this is going to be our test code and will represent the expected functionality).

The reason we write our test code before writing our actual application code is because it ultimately means that any application code we do produce will exist because it was actually used.

That's an important point I'm making and so let me take a moment to clarify it in more detail.

Writing Test Code Before Application Code

Typically, if you write your application code first (so you're not doing TDD), then you will find yourself writing code that at some point in the future is over engineered and potentially obsolete. Through the process of refactoring or changing requirements, you may find that some functions will fail to ever be called.

This is why TDD is considered the better practice and the preferred development method to use, because every line of code you produce will has been produced for a reason: to get a failing specification (your actual business requirement) to pass. That's a very powerful thing to keep in mind.

Here is our test code:

You may notice the code comments at the end of each line:

  • Given
  • When
  • Then

These are a form of BDD (Behaviour-Driven Development) terminology. I included them for readers who are more familiar with BDD (Behavior-Driven Development) and who were interested in how they can equate these statements with TDD.

The first thing we do inside this file is load spec_helper.rb (which is found in the same directory as our spec file). We'll come back and look at the contents of that file in a moment.

Next we have two code blocks that are specific to RSpec:

  • describe 'x' do
  • it 'y' do

The first describe block should adequately describe the specific class/module we're working on and providing tests for. You could very well have multiple describe blocks within a single specification file.

There are many different theories on how to use describe and it description blocks. I personally prefer simplicity and so I'll use the identifiers for the Class/Modules/Methods that we'll be testing. But you often find some people who prefer to use full sentences for their descriptions. Neither is right or wrong, just personal preference.

The it block is different and should always be placed inside a describe block. It should explain how we want our application to work.

Again, you could use a normal sentence to describe the requirements, but I've found that sometimes doing so can cause the descriptions to be too explicit, when they should really be more implicit. Being less explicit reduces the chances of changes to your functionality, causing your description to become outdated (having to update your description every time minor functionality changes occur is more of a burden than a help). By using the identifier of the method we're testing (for example, the name of the method we're executing) we can avoid that issue.

The contents of the it block is the code we're going to test.

In the above example, we create a new instance of the class RSpecGreeter (which doesn't exist yet). We send the message greet (which also doesn't exist yet) to the instantiated object created (note: these two lines are standard Ruby code at this point).

Finally, we tell the testing framework that we expect the outcome of calling the greet method to be the text "Hello RSpec!", by using the RSpec syntax: eq(something).

Notice how the syntax allows it to be easily read (even by a non-technical person). These are known as assertions.

There's a lot of different RSpec assertions and we won't go into the details, but feel free to review the documentation to see all the features RSpec provides.

Creating a Helper for Our Test

There is a certain amount of boilerplate required for our tests to run. In this project, we only have one specification file but in a real project you're likely to have dozens (depending on the size of your application).

To help us reduce the boilerplate code, we'll place it inside of a special helper file that we'll load from our specification files. This file will be titled spec_helper.rb.

This file will do a couple of things:

  • tell Ruby where our main application code is located
  • load our application code (for the tests to run against)
  • load the pry gem (helps us debug our code; if we need to).

Here is the code:

Note: the first line may look a bit cryptic, so let me explain how it works. Here we're saying we want to add the /lib/ folder to Ruby's $LOAD_PATH system variable. Whenever Ruby evaluates require 'some_file' it has a list of directories it will try and locate that file. In this instance, we're making sure that if we have the code require 'example' that Ruby will be able to locate it because it'll check our /lib/ directory and there, it'll find the file specified. This is a clever trick you will see used in a lot of Ruby gems, but it can be quite confusing if you've never seen it before.

Application Code

Our application code is going to be inside a file titled example.rb.

Before we begin writing any application code, remember that we're doing this project TDD. So we're going to let the tests in our specification file guide us on what to do first.

Let's begin by assuming you're using guard to run your tests (so every time we make a change to example.rb, Guard will notice the change and proceed to run example_spec.rb to make sure our tests pass).

For us to do TDD properly, our example.rb file will be empty and so if we open the file and 'save' it in its current state, then Guard will run and we'll discover (unsurprisingly) that our test will fail:


Now before we go any further, let me clarify again that TDD is based on the premise that every line of code has a reason to exist, so don't start racing ahead and writing out more code than you need. Only write the minimum amount of code required for the test to pass. Even if the code is ugly or doesn't fulfill the full functionality.

The point of TDD is to have a tight feedback loop, also known as 'red, green, refactor'). What this means in practice is:

  • write a failing test
  • write the least amount of code to get it to pass
  • refactor the code

You'll see in a moment that because our requirements are so simple, there is no need for us to refactor. But in a real project with much more complex requirements, you'll likely need to take the third step and refactor the code you entered, to get the test to pass.


Coming back to our failing test, as you can see in the error, there is no RSpecGreeter class defined. Let's fix that and add the following code and save the file so our tests run:

This will result in the following error:

Now we can see that this error is telling us the method greet doesn't exist, so let's add it and then again save our file to run our tests:

OK, we're almost there. The error we get now is:

RSpec is telling us that it was expecting to see Hello RSpec! but instead it got nil (because we defined the greet method but didn't actually define anything inside the method and so it returns nil).

We'll add the remaining piece of code that would get our test to pass:

There we have it, a passing test:

We're done here. Our test is written and the code is passing.

Conclusion

So far we've applied a Test-Driven Development process to building our application, alongside utilizing the popular RSpec testing framework.

We're going to leave it here for now. Come back and join us for part two where we'll look at more RSpec specific features as well as using the Pry gem to help you debug and write your code.

Tags:

Comments

Related Articles