Wednesday, June 24, 2015

Take control of your rspec test suite, Part 1

Let me tell you a little story about how I took our test suite from 25 minutes run time to just under 3 minutes while still testing all the same functionality.

A large application can generate a very unwieldy test suite. On one project I worked on, the test suite took 45 minutes to run on our CI, and when I tried to run the whole thing locally, I stopped it at the 1 1/2 hour mark when it looked like it still had a ways to go.

I can live with a suite like that for awhile, because I just run the tests that are specific to the work I’m doing. However, there are times when I’m working on something that could potentially affect several different places in the app, and I may not know exactly where those places are, so I have to run the whole suite. Waiting 45 minutes to be able to finish my feature is NOT cool, especially since that will likely have to happened a few times to ensure all tests are fixed.

On that project, I was limited with what I could do on the test suite due to budget constraints. However, on another project I worked on, our test suite took 25 minutes to run, but the project manager saw the financial benefit of trimming this number down and so let me spend some time fixing our tests.

I’ll be writing a few different posts on this topic. Step 1 is the easiest part: gathering data. Often, only a handful of tests take the longest to run, so we want to find out which of our tests are “losers” and try to refactor them into “winners.”

Thankfully, rspec gives us a command to do just that:

rspec --profile
# or rspec -p

This will run the entire suite, then list out the 10 examples that took the longest to run. If you want more than 10, you can specify the number you want as an argument, i.e. rspec -p 25.

You can also use this when running on certain test groups. So, if you wanted to find out the 17 slowest feature specs:

rspec -p 17 spec/features

Now you know the worst offenders. Stay tuned for part 2 where we delve into how to fix them up.

Thursday, June 18, 2015

Hack your Rspec setup using tags

I ran into an issue in rspec the other day where I had a before block to setup about 10 different tests, but I needed to write one more that was similar enough that it belonged in the same context but it required slightly different setup so the before block I already defined.

What to do? I could break out the existing tests into a nested context block and use the setup there, then do another nexted context block for my new test, but I really didn’t want to do that because all these tests should belong in the same context. I just wanted to skip the before block on one specific test.

Rspec tags to the rescue!

In this case, my new test was a JS test and all others were not (which is partly why the setup was different. So, the test was already tagged js: true, and in mybefore` block, I modified it like so:

before(:each) do |test|
  # Run this only on tests NOT tagged with js: true
  if test.example.metadata[:js] == nil
    # Existing setup code
  end
end

Works like a charm, and I still have all my related tests within the same context like I wanted.

Hat Tip: http://stackoverflow.com/questions/27864080/how-can-i-skip-some-setup-for-specific-rspec-tags

Thursday, June 11, 2015

Git stash is like a little mini save state

I’ve used this so many times and it has probably saved me all kinds of time, so I wanted to share.

Say you’ve just done a bunch of work on a new feature, modified 12 different files, and you haven’t committed anything yet. First of all:



But I know we’ve all done that. Now let’s say that you did all this on the wrong branch by accident. Sometimes git will let you switch branches without committing and take your work with you. I don’t know the hard and fast rules on how that works, and it seems to never work that way when I want it to.

If all of the work was in one file, it’d be simple enough to do some copypasta into the file on a new branch. Since we have workin a bunch of different files, that’s a no go.

Luckily, git gives us something called git stash.

Run git stash on all of your uncommitted work, and it will save it to a stash on your system. You can then shift to your new branch, and retrieve the stash by running git stash apply. Pretty cool!

Now let’s say you got yourself into a REAL mess. Let’s say that you did a bunch of work on the wrong branch, so you git stash-ed it, but you didn’t apply that stash to the correct branch. Then let’s say you got hungry and decided it was peanut butter jelly time. Then say you came back from lunch and did a bunch more work on the wrong branch.

You can git stash that work too, but when you go to apply all of your work (both stashes), it only applies the most recent. What happened to the first stash you made?

It’s still saved, but to access it, you have to run git stash list. This will show all of your stashes with the branch name where they were stashed from, and the most recent comment at the time of stashing. For example:

stash@{0}: WIP on damn_the_torpedoes: 240228e Merge pull request #384 from supremebeing7/jam_radar
stash@{1}: WIP on fix_thrusters: 7ed822 Merge pull request #383 from supremebeing7/add_lasers

To apply a particular stash, get the stash key (the stash@{x} at the beginning of the line) and run git stash apply {stash_key}. For example, if I want the work that I stashed on my fix_thrusters branch, I should run

git stash apply stash@{1}

Hopefully you get as much utility out of this as I have.

Tuesday, June 2, 2015

Quick and dirty nil and empty string handling in Ruby

I've run across issues with empty strings and nil values quite a bit lately - seems like mostly in relation to an API interfacing with iOS. Here are two quick tips:

To ensure that no nil values are given, I can call .to_s on any string values or values that should be strings. If one of them is nil, it will convert it to an empty string. If it's already a string, it will just leave it alone.

How about going the other way? .presence is the key. Call .presence on an empty string and it will give back nil. If the string isn't empty it will return the original string.

TL;DR:


"".presence 
# => nil 

"hey".presence 
# => "hey" 

nil.to_s 
# => ""

"hey".to_s 
# => "hey"