Sweep Unused Code into the Dustbin with rcov

Joe Ferris

One of the oldest tools in the Ruby testing utility belt is rcov. Most developers know that they can use rcov to find parts of their application code that tests aren’t exercising at all. However, playing with rcov’s options a bit can help you clean up your application after refactoring or removing features. These techniques all rely on a well-tested application, so make sure you start with 100% C0 coverage.

Aggregate coverage

If you’re like us, you’re using Cucumber to test drive your applications from the customer’s perspective, and then filling in the gaps with RSpec coverage for edge cases. We’ve grown comfortable with the idea of aggregate C0 coverage between Cucumber and RSpec, with neither tool resulting in 100% coverage by itself. Splitting up coverage like this makes a tool like RCov even more important, but it’s a little trickier to get this working.

RCov supports an --aggregate option that will look at coverage shared between several test runs, regardless of the tool used for testing. If you’re using RSpec, you’ll want a task like this:

RSpec::Core::RakeTask.new(:rspec_aggregate) do |task|
  task.pattern = 'spec/**/*_spec.rb'
  task.rspec_opts = "--format progress"
  task.rcov = true
  task.rcov_opts = "--exclude osx\/objc,spec,gems\/ " +
                    "--rails --aggregate tmp/coverage.data"
end

You can get Cucumber coverage as well with this guy:

Cucumber::Rake::Task.new(:cucumber_aggregate) do |task|
  task.rcov = true
  task.rcov_opts = "--exclude osx\/objc,gems\/,spec\/,features\/ " +
                    "--rails --aggregate tmp/coverage.data -o 'coverage'"
end

In case anything goes wrong, you’ll want to clean up coverage in between runs:

task :clean_aggregate do
  rm "tmp/coverage.data" if File.exist?("tmp/coverage.data")
end

You probably want a task to run these all together:

desc "Run aggregate coverage from rspec and cucumber"
task :rcov => ["rcov:clean_aggregate",
                "rcov:rspec_aggregate",
                "rcov:cucumber_aggregate"]

Now you can run rake rcov to get an HTML report of your combined RSpec and Cucumber coverage.

Finding unused application code

We hit all the “happy paths” with Cucumber, and we generally hit the failure UI as well. For example, we’ll test the successful submission of a form and one failed submission in Cucumber, and then we’ll test all the individual validations that cause the form to fail from RSpec. That approach hits most of the application code with Cucumber, and although just running every line doesn’t indicate decent coverage, it can give you an idea of which parts of the application code are actually in use. You can generate an HTML report of application code that isn’t reached by Cucumber using a task like this:

desc "Find potentially unused app code"
Cucumber::Rake::Task.new(:unused) do |task|
  task.rcov = true
  task.rcov_opts = "--exclude osx\/objc,spec,gems\/,features\/ " +
                    "--rails --only-uncovered"
end

This will generate a report for each file containing potentially unused application.

Here’s an example line from a generated coverage/index.html:

Unused file

If you click on the file, you’ll see unused portions highlighted in red:

Unused application code

We rely on combined coverage from Cucumber and RSpec, but anything that isn’t used in Cucumber is worth checking out. In this example, we quickly found out that this method could be removed. Removing unused methods and classes is a cheap and effective way to keep your application code small and readable.

Finding unused Cucumber step definitions

Cucumber’s mapped step definitions are useful for rapid testing without considering the implementation up front. However, an evolving application quickly develops a set of step definitions that aren’t used, cluttering up the features directory and slowing things down. You can use rcov to quickly identify steps that aren’t in use.

desc "Find unused cucumber step definitions"
Cucumber::Rake::Task.new(:steps) do |task|
  task.rcov = true
  task.rcov_opts = "--exclude osx\/objc,spec,gems\/,app\/,lib\/" +
                    "--rails --only-uncovered"
end

Notice that we added the app and lib directories, to ignore application code that isn’t exercised by Cucumber, and removed the features directory, so that it complains about unused feature code.

If you open coverage/index.html and click around, you’ll find steps highlighted in red:

Unused step definition

If something is in red, it’s guaranteed that your test suite isn’t using it. You may want to keep some steps around that are never used (such as debugging steps), but most can safely be removed. Doing this regularly can keep your step definitions under control.

Taking things further

These examples are all wrapped up in Gist so that you can see them together: https://gist.github.com/795435

If you have improvements or corrections, please fork away!

There are a few other useful options in rcov:

  • --text-coverage - print coverage to the console. Useful for terminal-oriented developers or for continuous integration. If you’re running this on CI, you may want to also add --no-color, which will disable ASCII color codes in the output.
  • --text-report - print a summary to the console.
  • --only-uncovered - removes files from the report with 100% coverage. Particularly useful when printed to the console or on CI.

Do you use rcov for anything else besides ensuring C0 coverage? If you have a cool recipe using rcov, let us know.