Testing Your Ruby Code With Guard, RSpec & Pry: Part 2

Welcome back! If you missed the first part of our journey so far, then you might want to go back and catch-up first.

So far, we've applied a Test-Driven Development process to building our application, alongside utilizing the popular RSpec testing framework. From here, we're going to investigate some other RSpec features as well as look into using the Pry gem to help you debug and write your code.

Other RSpec Features

Let's take a moment to review some other RSpec features that we've not needed in this simple example application, but you may find useful working on your own project.

Pending Statement

Imagine you write a test but you're interrupted, or you need to leave for a meeting and haven't yet completed the code required to get the test to pass.

You could delete the test and re-write it later when you're able to come back to your work. Or alternatively you could just comment the code out, but that's pretty ugly and definitely no good when using a version control system.

The best thing to do in this situation is to define our test as 'pending' so whenever the tests are run, the test framework will ignore the test. To do this you need to use the pending keyword:

Set-Up and Tear-Down

All good testing frameworks allow you to execute code before and after each test is run. RSpec is no different.

It provides us before and after methods which allows us to set-up a specific state for our test to run, and to then clean up that state after the test has run (this is so the state doesn't leak and effect the outcome of subsequent tests).

Context Blocks

We've already seen the describe block; but there is another block which is functionally equivalent called context. You can use it every where you would use describe.

The difference between them is subtle but important: context allows us to define a state for our test. Not explicitly though (we don't actually set the state by defining a context block - it instead is for readability purposes so the intent of the following code is clearer).

Here is an example:

Stubs

We can use the stub method to create a fake version of an existing object and to have it return a pre-determined value.

This is useful in preventing our tests from touching live service APIs, and guiding our tests by giving predictable results from certain calls.

Imagine we have a class called Person and that this class has a speak method. We want to test that method works how we expect it to. To do this we'll stub the speak method using the following code:

In this example, we say that 'any instance' of the Person class should have its initialize method stubbed so it returns the object bob.

You'll notice that bob is itself a stub which is set-up so that any time code tries to execute the speak method it will return "hello".

We then proceed to create a new Person instance and pass the call of instance.speak into RSpec's expect syntax.

We tell RSpec that we're expecting that call to result in the String "hello".

Consecutive Return Values

In the previous examples we've use the RSpec feature and_return to indicate what our stub should return when it's called.

We can indicate a different return value each time the stub is called by specifying multiple arguments to the and_return method:

Mocks

Mocks are similar to Stubs in that we are creating fake versions of our objects but instead of returning a pre-defined value we're more specifically guiding the routes our objects must take for the test to be valid.

To do that we use the mock method:

In the above example we create a new Object instance and then call its testing method.

Behind the scenes of that code we expect the testing method to be called with the value 'content'. If it isn't called with that value (which in the above example it's not) then we know that some piece of our code hasn't functioned properly.

Subject Block

The subject keyword can be used in a couple of different ways. All of which are designed to reduce code duplication.

You can use it implicitly (notice our it block doesn't reference subject at all):

You can use it explicitly (notice our it block refers to subject directly):

Rather than constantly referencing a subject within your code and passing in different values for instantiation, for example:

You can instead use subject along with let to reduce the duplication:

There are many more features available to RSpec but we've looked at the most important ones that you'll find yourself using a lot when writing tests using RSpec.

Randomized Tests

You can configure RSpec to run your tests in a random order. This allows you to ensure that none of your tests have any reliance or dependency on the other tests around it.

You can set this via the command using the --order flag/option. For example: rspec --order random.

When you use the --order random option RSpec will display the random number it used to seed the algorithm. You can use this 'seed' value again when you think you've discovered a dependency issue within your tests. Once you've fixed what you think is the issue you can pass the seed value into RSpec (e.g. if the seed was 1234 then execute --order random:1234) and it will use that same randomized seed to see if it can replicate the original dependency bug.

Global Configuration

You've seen we've added a project specific set of configuration objects within our Rakefile. But you can set configuration options globally by added them to a .rspec file within your home directory.

For example, inside .rspec:

Debugging With Pry

Now we're ready to start looking into how we can debug our application and our test code using the Pry gem.

It's important to understand that although Pry is really good for debugging your code, it is actually meant as an improved Ruby REPL tool (to replace irb) and not strictly debugging purposes; so for example there are no built-in functions such as: step into, step over or step out etc that you would typically find in a tool designed for debugging.

But as a debugging tool, Pry is very focused and lean.

We'll come back to debugging in a moment, but let's first review how we'll be using Pry initially.

Updated Code Example

For the purpose of demonstrating Pry I'm going to add more code to my example application (this extra code doesn't effect our test in any way)

You'll notice we've added some extra methods, instance and class properties. We also make calls to two of the new methods we've added from within our greet method.

Lastly, you'll notice the use of binding.pry.

Setting Break-Points With binding.pry

A break-point is a place within your code where execution will stop.

You can have multiple break-points set within your code and you create them using binding.pry.

When you run your code you'll notice that the terminal will stop and place you inside your application's code at the exact spot your binding.pry was placed.

Below is an example of how it might look...

From this point Pry has access to the local scope so you can use Pry much like you would in irb and start typing in for example variables to see what values they hold.

You can run the exit command to exit Pry and for your code to continue executing.

Finding Where You Are: whereami

When using lots of binding.pry break points it can be difficult to understand where in the application you are.

To get a better context of where you are at any point, you can use the whereami command.

When run on its own you'll see something similar to when you used binding.pry (you'll see the line on which the break-point was set and a couple of lines above and below that). The difference being is if you pass an extra numerical argument like whereami 5 you'll see five additional lines above where the binding.pry was placed. You could request to see 100 lines around the current break-point for example.

This command can help orientate you within the current file.

Stack Trace: wtf

The wtf command stands for "what the f***" and it provides a full stack trace for the most recent exception that has been thrown. It can help you understand the steps leading up to the error that occurred.

Inspecting: ls

The ls command displays what methods and properties are available to Pry.

When run it will show you something like...

In the above example we can see that we have four public methods (remember we updated our code to include some additional methods and then test and test= were created when using Ruby's attr_accessor short hand).

It also displays other class and local variables Pry can access.

Another useful thing you can do is to grep (search) the results for only what you're interested in. You'll need to have an understanding of Regular Expressions but it can be a handy technique. Here is an example...

In the above example we're using the -p and -G options/flags which tell Pry we only want to see public and private methods and we use the regex ^p (which means match anything starting with p) as our search pattern to filter the results.

Running ls --help will also show you all available options.

Changing Scope: cd

You can change the current scope by using the cd command.

In our example if we run cd ../pubs it'll take us to the result of that method call.

If we now run whereami you'll see it will display Inside "I'm a test variable".

If we run self then you'll see we get "I'm a test variable" returned.

If we run self.class we'll see String returned.

You can move up the scope chain using cd .. or you can go back to the top level of the scope using cd /.

Note: we could add another binding.pry inside the pubs method and then our scope would be inside that method rather than the result of the method.

Seeing How Deep You Are: nesting

Consider the previous example of running cd pubs. If we run the nesting command we'll get a top level look on the number of contexts/levels Pry currently has:

From there we can run exit to move back to the earlier context (for example, inside the greet method)

Running exit again will mean we're closing the last context Pry has and so Pry finishes and our code continues to run.

Locate Any Method: find-method

If you're not sure where to find a particular method then you can use the find-method command to show you all files within your code base that has a method that matches what you're searching for:

You can also use the -c option/flag to search the content of files instead:

Classic Debugging: next, step, continue

Although the above techniques are useful, it's not really 'debugging' in the same sense as what you're probably used to.

For most developers their editor or browser will provide them with a built-in debugging tool that lets them actually step through their code line by line and follow the route the code takes until completion.

As Pry is developed to be used as a REPL that's not to say it's not useful for debugging.

A naive solution would be to set multiple binding.pry statements through out a method and use ctrl-d to move through each break-point set. But that' still not quite good enough.

For step by step debugging you can load the gem pry-nav...

This gem extends Pry so it understands the following commands:

  • Next (move to the next line)
  • Step (move to the next line and if it's a method, then move into that method)
  • Continue (Ignore any further break-points in this file)

Continuous Integration With Travis-CI

As an added bonus let's integrate our tests with the online CI (continuous integration) service Travis-CI.

The principle of CI is to commit/push early and often to avoid conflicts between your code and the master branch. When you do (in this case we're committing to GitHub) then that should kick off a 'build' on your CI server which runs the relevant tests to ensure all is working as it should be.

Now with TDD as your primary development methodology you're less likely to incur bugs every time you push because your tests are an integral part of your development workflow and so before you push you'll already be aware of bugs or regressions. But this doesn't necessarily protect you from bugs that occur from integration tests (where all code across multiple systems are run together to ensure the system 'as a whole' is functioning correctly).

Regardless, code should never be pushed directly to your live production server any way; it should always be pushed first to a CI server to help catch any potential bugs that arise from differences between your development environment and the production environment.

A lot of companies have more environments still for their code to pass through before it reaches the live production server.

For example, at BBC News we have:

  • CI
  • Test
  • Stage
  • Live

Although each environment should be identical in set-up, the purpose is to implement different types of testing to ensure as many bugs are caught and resolved before the code reaches 'live'.

Travis-CI

Travis CI is a hosted continuous integration service for the open source community. It is integrated with GitHub and offers first class support for multiple languages

What this means is that Travis-CI offers free CI services for open-source projects and also has a paid model for businesses and organizations who want to keep their CI integration private.

We'll be using the free open-source model on our example GitHub repository.

The process is this:

  • Register an account with GitHub
  • Sign into Travis-CI using your GitHub account
  • Go to your "Accounts" page
  • Turn "on" any repositories you want to run CI on
  • Create a .travis.yml file within the root directory of your project and commit it to your GitHub repository

The final step is the most important (creating a .travis.yml file) as this determines the configuration settings for Travis-CI so it knows how to handle running the tests for your project.

Let's take a look at the .travis.yml file we're using for our example GitHub repository:

Let's break this down piece by piece...

First we specify what language we're using in our project. In this case we're using Ruby: language: ruby.

Because running Bundler can be a bit slow and we know that our dependencies aren't going to change that often we can choose to cache the dependencies, so we set cache: bundler.

Travis-CI uses RVM (Ruby Version Manager) for installing Rubies on their servers. So we need to specify what Ruby versions we want to run our tests against. In this instance we've chosen 2.0 and 1.9.3 which are two popular Ruby versions (technically our application uses Ruby 2 but it's good to know our code passes in other versions of Ruby as well):

To run our tests we know that we can use the command rake or rake spec. Travis-CI by default runs the command rake but because of how Gems are installed on Travis-CI using Bundler we need to change the default command: script: 'bundle exec rake spec'. If we didn't do this then Travis-CI would have an issue locating the rspec/core/rake_task file which is specified within our Rakefile.

Note: if you have any issues regarding Travis-CI then you can join the #travis channel on IRC freenode to get help answering any questions you may have. That's where I discovered the solution to my issue with Travis-CI not being able to run my tests using its default rake command and the suggestion of overwriting the default with bundle exec rake resolved that issue.

Next, because we're only interested in running our tests we can pass additional arguments to Travis-CI to filter gems we don't want to bother installing. So for us we want to exclude installing the gems grouped as development: bundler_args: --without development (this means we are excluding gems that are only really used for development and debugging such as Pry and Guard).

It's important to note that originally I was loading Pry within our spec_helper.rb file. This caused a problem when running the code on Travis-CI, now that I was excluding 'development' gems. So I had to tweak the code like so:

You can see that now the Pry gem is only require'ed if an environment variable of APP_ENV is set to debug. This way we can avoid Travis-CI from throwing any errors. This does mean that when running your code locally you'd need to set the environment variable if you wanted to debug your code using Pry. The following shows how this could be done in one line:

There were two other changes I made and that was to our Gemfile. One was to make it clearer what gems were required for testing and which were required for development, and the other was explicitly required by Travis-CI:

Looking at the above updated Gemfile we can see we've moved the RSpec gem into a new test group, so now it should be clearer what purpose each gem has. We've also added a new gem 'rake'. The Travis-CI documentation states this needed to be specified explicitly.

The next section is optional and it allows you to white list (or black list) certain branches on your repository. So by default Travis-CI will run tests against all your branches unless you tell it otherwise. In this example we're telling it we only want it to run against our master branch:

We could tell it to run every branch 'except' a particular branch, like so:

The final section tells Travis-CI where to send notifications to when a build fails or succeeds:

You can specify multiple email addresses if you wish:

You can be more specific and specify what you want to happen on either a failure or a success (for example, more people will only be interested if the tests fail rather than getting an email every time they pass):

The above example shows that the recipient will never receive an email if the tests pass but will get notified if the failure status changes (the default value for both is always which means you'll always be notified regardless of what the status result).

Note: when you explicitly specify a on_failure or on_success you need to move the email address(es) inside of recipients key.

Conclusion

This is the end of our two part look into RSpec, TDD and Pry.

In part one we managed to write our application using the TDD process and the RSpec testing framework. In this second half we've also utilized Pry to show how we can more easily debug a running Ruby application. Finally we were able to get our tests set-up to run as part of a continuous integration server using the popular Travis-CI service.

Hopefully this has given you enough of a taste so you are keen to investigate each of these techniques further.

Tags:

Comments

Related Articles