Up until two weeks ago, I had one great fear: testing. And, to be clear, when I say “fear”, what I actually mean is sheer terror.
My test-writing anxiety stems from the fact that I’ve never really had to do it before. I mean, I’ve had to make tons of tests pass, which means that I read other people’s tests all the time. Yet I’ve somehow made it thus far in my coding career without ever having to write relatively complex tests of my own. But that all changed a few weeks ago, when I was forced to finally confront my fear of testing.
The thing about conquering fears, however, is that usually involves doing the very thing that you’re afraid of. So, I spent the better portion of a week learning how and when to write tests, all while encountering a couple painful bugs along the way. It was not a fun week, but the good news is that I can write a fully-functioning test suite now! And now that I know more about testing, I actually find it kind of fun – so fun, in fact, that I’m going to share it with you!
Ain’t No Spec Like Rspec
Before we get into the how and when of testing, we first need to setup our Rails application with
rspec, a behavior-driven development framework built specifically for testing in Ruby.
We’ll first want to add
rspec-rails to our the development and test group in our
1 2 3
Next, we’ll run a quick
bundle install, and then generate a
/spec folder by running
rails generate rspec:install. We now have access to a
spec_helper.rb file inside of our
Finally, we’ll want to add files for everything that we want to test. But let’s start simple for now and just test our
ReadingList model. The path to this spec file should be
/spec/models/reading_list_spec.rb, so we’ll need to add a
models directory and a
Once we’ve done that, we can check that everything is setup properly by running our
1 2 3 4 5 6
You know what needs to happen next, right? It’s time for us to write some tests.
Okay, I feel your pain. But I promise, we’re going to get through this together.
Knowing What To Test
I’ve found that the best way to start writing tests is by picking one section to work on first. Otherwise, it can just be so overwhelming and might make you want to give up completely. Let’s take a look at what our
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
Whoa, this seems like a lot. But fear not! Programming is nothing more than breaking down big problems into smaller, bite-sized pieces. And that’s exactly what we’ll do when writing these tests.
Let’s look at the
percentage_read method to start. This is the instance method that we’ll actually want to call somewhere in our view. It uses the number of books marked
read (which will always be a boolean
false value), and calculates the
User’s reading progress on the list, returning a percentage.
But even though this is the method we want to test, a deeper look reveals that it actually relies and calls upon three other methods:
calculate_percentage. This should be a big red flag, because it means that we need to test these three methods individually, first. The flow of our code is actually directing us in our test-writing process: we can decide which tests to write and in which order by looking at our method’s dependencies.
So, let’s hop to it:
- We’ll start by first requiring
reading_list_spec.rb, and stubbing out our tests with a block:
1 2 3 4 5 6
We can use a
describe block to break up our tests into different sections. They will come in handy as our tests start to grow, and will make our test suite easier to read – not just when we come back to look at them later, but also when another developer digs through our code. The
# symbol before our method name denotes that
percentage_read is an instance method, another important distinction to make as we go about adding more tests.
- Next, we’ll describe what our method should do by using
1 2 3 4 5 6 7 8 9 10 11 12 13
- Now we need to add some data – but not too much! We only want enough data to test the functionality of our method. Let’s create a list with two books, one marked read and the other not marked read. We’ll add this before our
1 2 3
Wait, what’s that
let doing in there? The answer is: something magical! It creates an instance of
ReadingList and makes a reference to it called
list, which is then accessible to us in each of our
it blocks. The
let syntax is an alternative to creating local variables inside every single one of our
- Finally, we’ll add some expectations for our model’s behavior when each method is called. Our finished test suite now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Each one of our tests is just a single line, specifying our exact expectations! Pretty awesome, right? This makes for relatively DRY code, which is pretty easy to understand.
Testing Your Assumptions
Now that we know how to write tests, it’s time to address the question of when and what to test. Here’s a good rule of thumb that I adhere to: test your assumptions. Whenever we write code, we make a ton of assumptions. The problem with making assumptions, however, is that you forget or don’t realize that you made them, and then they end up coming back to screw you.
In fact, even the tests we just wrote are based on a lot of assumptions. And there are a lot of things that we haven’t considered. For example:
- Does the
readattribute on a
ReadingListobject only accept a
booleanvalue? What if someone tries to pass a non-boolean value as
- What is the default value of the
- What if
readis nil – what will break?
- What if the return value of
Just FYI, I discovered the answer to number 4, which looks like this:
Uh oh…I did a bad thing: pic.twitter.com/uFZEkyCPRl— Vaidehi Joshi (@vaidehijoshi) April 3, 2015
This is all to say that we must write tests for behavior that should and should not occur. We’d probably want to write validations to prevent
nil values, and we’d definitely want to raise an error whenever we try to divide by
0. We aren’t just testing for what we can see – we also need to test for things we can’t see, and any edge cases that we can think of.
Learning the how, when, and what of testing is a process that comes with time and practice. The more tests you write, the better you’ll get at testing. Of course, there are few tips and tricks of the testing trade that can very quickly and easily save you a lot of heartache.
Tune in again next Tuesday, when I’ll delve into generating fixtures for test data using FactoryGirl – a trick that’s going to make your testing life so much easier.
rspectests have an
itblock, which describes what behavior is expected. This block should never be too big, and contains an assertion of what expected value should be returned.
letsyntax allows for lazy evaluation and keeps you from having to create a new instance of an object inside of every single
itblock. Check out more on the
let!helper methods over on this Stack Overflow answer or on this blog post.
describeblocks to divide up your tests into sections, based on functionality and code cohesion. You can also use
contextblocks to assert different scenarios that could occur during one method call. Read about the difference between describe and context.
- Find out more about different rspec testing conventions at Better Specs.