---
title: "Ruby 2.5 supports measuring branch and method coverages"
description:
  "Ruby 2.5 has added support to measure branch and method coverage using
  `Coverage` library."
canonical_url: "https://www.bigbinary.com/blog/ruby-2-5-supports-measuring-branch-and-method-coverages"
markdown_url: "https://www.bigbinary.com/blog/ruby-2-5-supports-measuring-branch-and-method-coverages.md"
---

# Ruby 2.5 supports measuring branch and method coverages

Ruby 2.5 has added support to measure branch and method coverage using
`Coverage` library.

- Author: Vishal Telangre
- Published: April 11, 2018
- Categories: Ruby 2.5, Ruby

Ruby comes with
[Coverage](https://ruby-doc.org/stdlib-2.5.0/libdoc/coverage/rdoc/Coverage.html),
a simple standard library for test coverage measurement for a long time.

## Before Ruby 2.5

Before Ruby 2.5, we could measure just the line coverage using `Coverage`.

Line coverage tells us whether a line is executed or not. If executed, then how
many times that line was executed.

We have a file called `score.rb`.

```ruby
score = 33

if score >= 40
  p :PASSED
else
  p :FAILED
end
```

Now create another file `score_coverage.rb`.

```ruby
require "coverage"

Coverage.start
load "score.rb"
p Coverage.result
```

We used `Coverage#start` method to measure the coverage of `score.rb` file.
`Coverage#result` returns the coverage result.

Let's run it with Ruby 2.4.

```ruby
$ RBENV_VERSION=2.4.0 ruby score_coverage.rb
:FAILED
{ "score.rb"=> [1, nil, 1, 0, nil, 1, nil] }
```

Let's look at the output. Each value in the array `[1, nil, 1, 0, nil, 1, nil]`
denotes the count of line executions by the interpreter for each line in
`score.rb` file.

This array is also called the "line coverage" of `score.rb` file.

A `nil` value in line coverage array means coverage is disabled for that
particular line number or it is not a relevant line. Lines like `else`, `end`
and blank lines have line coverage disabled.

Here's how we can read above line coverage result.

- Line number 1 (i.e. 0th index in the above result array) was executed once.
- Coverage was disabled for line number 2 (i.e. index 1) as it is blank.
- Line number 3 (i.e. index 2) was executed once.
- Line number 4 did not execute.
- Coverage was disabled for line number 5 as it contains only `else` clause.
- Line number 6 was executed once.
- Coverage was disabled for line number 7 as it contains just `end` keyword.

## After Ruby 2.5

There was a [pull request](https://github.com/ruby/ruby/pull/511) opened in 2014
to add method coverage and decision coverage metrics in Ruby. It was
[rejected](https://github.com/ruby/ruby/pull/511#issuecomment-328753499) by
[Yusuke Endoh](https://github.com/mame) as he saw some issues with it and
mentioned that he was also working on a similar implementation.

In Ruby 2.5, Yusuke Endoh
[added branch coverage and method coverage feature](https://bugs.ruby-lang.org/issues/13901)
to the `Coverage` library.

Let's see what's changed in `Coverage` library in Ruby 2.5.

### Line Coverage

If we execute above example using Ruby 2.5, we will see no change in the result.

```ruby
$ RBENV_VERSION=2.5.0 ruby score_coverage.rb
:FAILED
{ "score.rb" => [1, nil, 1, 0, nil, 1, nil] }
```

This behavior is maintained to ensure that the `Coverage#start` API stays 100%
backward compatible.

If we explicitly enable `lines` option on `Coverage#start` method in the above
`score_coverage.rb` file, the coverage result will be different now.

```ruby
require "coverage"

Coverage.start(lines: true)
load "score.rb"
p Coverage.result
```

```ruby
$ RBENV_VERSION=2.5.0 ruby score_coverage.rb
:FAILED
{ "score.rb" => {
    :lines => [1, nil, 1, 0, nil, 1, nil]
  }
}
```

We can see that the coverage result is now a hash which reads that the
`score.rb` file has `lines` coverage as `[1, nil, 1, 0, nil, 1, nil]`.

### Branch Coverage

Branch coverage helps us identify which branches are executed and which ones are
not executed.

Let's see how to get branch coverage.

We will update the `score_coverage.rb` by enabling `branches` option.

```ruby
require "coverage"

Coverage.start(branches: true)
load "score.rb"
p Coverage.result
```

```ruby
$ RBENV_VERSION=2.5.0 ruby score_coverage.rb
:FAILED
{ "score.rb" =>
  { :branches => {
      [:if, 0, 3, 0, 7, 3] => {
        [:then, 1, 4, 2, 4, 15] => 0,
        [:else, 2, 6, 2, 6, 15] => 1
      }
    }
  }
}
```

Here is how to read the data in array.

```ruby
[
  BRANCH_TYPE,
  UNIQUE_ID,
  START_LINE_NUMBER,
  START_COLUMN_NUMBER,
  END_LINE_NUMBER,
  END_COLUMN_NUMBER
]
```

Please note that column numbers start from 0 and line numbers start from 1.

Let's try to read above printed branch coverage result.

`[:if, 0, 3, 0, 7, 3]` reads that `if` statement starts at line 3 & column 0 and
ends at line 7 & column 3.

`[:then, 1, 4, 2, 4, 15]` reads that `then` clause starts at line 4 & column 2
and ends at line 4 & column 15.

Similarly, `[:else, 2, 6, 2, 6, 15]` reads that `else` clause starts at line 6 &
column 2 and ends at line 6 & column 15.

Most importantly as per the branch coverage format, we can see that the branch
from `if` to `then` was never executed since `COUNTER` is `0`. The another
branch from `if` to `else` was executed once since `COUNTER` is `1`.

### Method Coverage

Measuring method coverage helps us identify which methods were invoked and which
were not.

We have a file `grade_calculator.rb`.

```ruby
students_scores = { "Sam" => [53, 91, 72],
                    "Anna" => [91, 97, 95],
                    "Bob" => [33, 69, 63] }

def average(scores)
  scores.reduce(&:+)/scores.size
end

def grade(average_score)
  case average_score
  when 90.0..100.0 then :A
  when 80.0..90.0 then :B
  when 70.0..80.0 then :C
  when 60.0..70.0 then :D
  else :F
  end
end

def greet
  puts "Congratulations!"
end

def warn
  puts "Try hard next time!"
end


students_scores.each do |student_name, scores|
  achieved_grade = grade(average(scores))

  puts "#{student_name}, you've got '#{achieved_grade}' grade."

  if achieved_grade == :A
    greet
  elsif achieved_grade == :F
    warn
  end

  puts
end
```

To measure method coverage of above file, let's create
`grade_calculator_coverage.rb` by enabling `methods` option on `Coverage#start`
method.

```ruby
require "coverage"

Coverage.start(methods: true)
load "grade_calculator.rb"
p Coverage.result
```

Let's run it using Ruby 2.5.

```ruby
$ RBENV_VERSION=2.5.0 ruby grade_calculator_coverage.rb
Sam, you've got 'C' grade.

Anna, you've got 'A' grade.
Congratulations!

Bob, you've got 'F' grade.
Try hard next time!

{ "grade_calculator.rb" => {
    :methods => {
      [Object, :warn, 23, 0, 25, 3] => 1,
      [Object, :greet, 19, 0, 21, 3] => 1,
      [Object, :grade, 9, 0, 17, 3] => 3,
      [Object, :average, 5, 0, 7, 3] => 3
    }
  }
}
```

The format of method coverage result is defined as shown below.

```ruby
[ CLASS_NAME,
  METHOD_NAME,
  START_LINE_NUMBER,
  START_COLUMN_NUMBER,
  END_LINE_NUMBER,
  END_COLUMN_NUMBER ]
```

Therefore, `[Object, :grade, 9, 0, 17, 3] => 3` reads that the `Object#grade`
method which starts from line 9 & column 0 to line 17 & column 3 was invoked 3
times.

## Conclusion

We can measure all coverages at once also.

```ruby
Coverage.start(lines: true, branches: true, methods: true)
```

What's the use of these different types of coverages anyway?

Well, one use case is to integrate this in a test suite and to determine which
lines, branches and methods are executed and which ones are not executed by the
test. Further, we can sum up these and evaluate total coverage of a test suite.

Author of this feature, Yusuke Endoh has released
[coverage-helpers](https://github.com/mame/coverage-helpers) gem which allows
further advanced manipulation and processing of coverage results obtained using
`Coverage#result`.

## Links

- [Human page](https://www.bigbinary.com/blog/ruby-2-5-supports-measuring-branch-and-method-coverages)
