How I Test

In a recent discussion on Google+, a friend of mine commented, "Test-Driven Development (TDD) and Behavior-Driven Development (BDD) is Ivory Tower BS." This prompted me to think about my first project, how I felt the same way then, and how I feel about it now. Since that first project, I've developed a rhythm of TDD/BDD that not only works for me, but for the client as well.

Ruby on Rails ships with a test suite, called Test Unit, but many developers prefer to use RSpec, Cucumber, or some combination of the two. Personally, I prefer the latter, using a combination of both.


RSpec

From the RSpec site:

RSpec is a testing tool for the Ruby programming language. Born under the banner of Behaviour-Driven Development, it is designed to make Test-Driven Development a productive and enjoyable experience.

RSpec provides a powerful DSL that is useful for both unit and integration testing. While I have used RSpec for writing integration tests, I prefer to use it only in a unit testing capacity. Therefore, I will cover how I use RSpec exclusively for unit testing. I recommend reading The RSpec Book by David Chelimsky and others for complete and in-depth RSpec coverage.


Cucumber

I've found the benefits of TDD/BDD far outweigh the cons.

Cucumber is an integration and acceptance testing framework that supports Ruby, Java, .NET, Flex, and a host of other web languages and frameworks. Its true power comes from its DSL; not only is it available in plain English, but it has been translated into over forty spoken languages.

With a human-readable acceptance test, you can have the customer sign off on a feature, before writing a single line of code. As with RSpec, I will only be covering Cucumber in the capacity in which I use it. For the complete rundown on Cucumber, check out The Cucumber Book.


The Setup

Let's first begin a new project, instructing Rails to skip Test Unit. Type the following into a terminal:

Within the Gemfile, add:

I mostly use RSpec to ensure that my models and their methods stay in check.

Here, we've put Cucumber and friends inside of the group test block. This ensures that they are properly loaded only in the Rails test environment. Notice how we also load RSpec inside of the development and test blocks, making it available in both environments. There are a few other gems. which I will briefly detail below. Don't forget to run bundle install to install them.

We need to run these gems' generators to set them up. You can do that with the following terminal commands:

At this point, we could begin writing specs and cukes to test our application, but we can set up a few things to make testing easier. Let's start in the application.rb file.

Inside the Application class, we override a few of Rails' default generators. For the first two, we skip the views and helpers generation specs.

These tests are not necessary, because we are only using RSpec for unit tests.

The third line informs Rails that we intend to use RSpec as our test framework of choice, and it should also generate fixtures when generating models. The final line ensures that we use factory_girl for our fixtures, of which are created in the spec/factories directory.


Our First Feature

To keep things simple, we're going to write a simple feature for signing into our application. For the sake of brevity, I will skip the actual implementation and stick with the testing suite. Here is the contents of features/signing_in.feature:

When we run this in the terminal with cucumber features/signing_in.feature, we see a lot of output ending with our undefined steps:

The next step is to define what we expect each of these steps to do. We express this in features/stepdefinitions/signin_steps.rb, using plain Ruby with Capybara and CSS selectors.

Within each of the Given, When, and Then blocks, we use the Capybara DSL to define what we expect from each block (except in the first one). In the first given block, we tell factory_girl to create a user stored in the user instance variable for later use. If you run cucumber features/signing_in.feature again, you should see something similar to the following:

We can see from the error message that our example fails on line 1 with an ArgumentError of the user factory not being registered. We could create this factory ourselves, but some of the magic we setup earlier will make Rails do that for us. When we generate our user model, we get the user factory for free.

As you can see, the model generator invokes factory_girl and creates the following file:

I won't go into great depth of factory_girl here, but you can read more in their getting started guide. Don't forget to run rake db:migrate and rake db:test:prepare to load the new schema. This should get the first step of our feature to pass, and start you down the road of using Cucumber for your integration testing. On each pass of your features, Cucumber will guide you to the pieces that it sees missing to make it pass.


Model Testing with RSpec and Shoulda

I mostly use RSpec to make sure that my models and their methods stay in check. I often also use it for some high level controller testing, but that goes into more detail than this guide allows for. We're going to use the same user model that we previously set up with our sign-in feature. Looking back at the output from running the model generator, we can see that we also got user_spec.rb for free. If we run rspec spec/models/user_spec.rb we should see the following output.

And if we open that file, we see:

The pending line gives us the output we saw in the terminal. We'll leverage Shoulda's ActiveRecord and ActiveModel matchers to ensure our user model matches our business logic.

We setup a few context blocks inside of our first describe block to test things like fields, validations, and associations. While there are not functional differences between a describe and a context block, there is a contextual one. We use describe blocks to set the state of what we are testing, and context blocks to group those tests. This makes our tests more readable and maintainable in the long run.

The first describe allows us to test against the User model in an unmodified state.

We use this unmodified state to test against the database with the Shoulda matchers grouping each by type. The next describe block sets up a user from our previously created user factory. Setting up the user with the let method inside of this block allows us to test an instance of our user model against known attributes.

Now, when we run rspec spec/models/user_spec.rb, we see that all of our new tests fail.

With each of these tests failing, we have the framework we need to add migrations, methods, associations, and validations to our models. As our application evolves, models expand and our schema changes, this level of testing provides us with protection for introducing breaking changes.


Conclusion

While we didn't cover too many topics in depth, you should now have a basic understanding of integration and unit testing with Cucumber and RSpec. TDD/BDD is one of the things developers either seem to do or don't do, but I've found that the benefits of TDD/BDD far outweigh the cons on more than one occasion.

Tags:

Comments

Related Articles