19
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SimpleCovのカバレッジ(網羅率)

Posted at

Testing Casual Talks#2に行ってきました。そういえばカバレッジの話がなかったなと思ったので、以前調査した結果を共有したいと思います。

バージョンとか

  • ruby (2.1.5)
  • simplecov (0.9.2)

カバレッジ(網羅率)とは

よく出てくるカバレッジは下記の3種類でしょう。

  • 命令網羅(C0)

    テスト内で実行されたメソッドの割合を意味する

  • 分岐網羅(C1)

    テスト内で実行された分岐の割合を意味する

  • 条件網羅(C2)

    テスト内で実行された分岐の組み合わせの割合を意味する

def test(x, y)
  if (x == 1) || (y == 2)
    puts "true"
  else
    puts "false"
  end
end

このようなコードがあったとしましょう。命令網羅の場合、どのような引数であれtestメソッドをテストしていれば、カバレッジは100%になります。分岐網羅の場合、例えばx = 1, y = 10x = 2, y = 10testメソッドを呼び出していれば、カバレッジは100%になります。条件網羅の場合、ifの前半がtrue/false、後半がtrue/falseという組み合わせがあるため、x = 1, y = 10x = 2, y = 2x = 1, y = 2x = 2, y = 10の4パターンでtestメソッドを呼び出していれば、カバレッジは100%になります。

SimpleCovのカバレッジ

SimpleCov gemのカバレッジが上記のうち、どれにあたるかというと、厳密にはどれにも該当していないというのが、正しいと思います。

例として、次のコードのカバレッジを計測してみます。
このコードはgithubにもおいてあるので、結果のhtmlなどは、そちらを参考にしてください。

simplecov_sample.rb
require "simplecov_sample/version"

module SimplecovSample
  class Sample
    def initialize(name = "")
      @name = name
    end

    [:test1, :test2, :test3].each do |method_name|
      class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
        def #{method_name}()    # def test1()
          "#{method_name.to_s}" #   "test1"
        end                     # end
      RUBY_EVAL
    end

    class_eval do
      def test_proc1
        "test_proc1"
      end
    end

    [:test4, :test5, :test6].each do |method_name|
      define_method(method_name) do
        method_name.to_s
      end
    end

    def name(upcase = false)
      if upcase
        @name.upcase
      else
        @name
      end
    end

    def name2(upcase = false)
      upcase ? @name.upcase : @name
    end

    def name3(upcase = false)
      return @name.upcase if upcase
      @name
    end

    def name4(count = 1)
      case count
      when 1
        @name
      when 2
        @name * 2
      else
        ""
      end
    end

    def mapped(enum)
      enum.map do
        1
      end
    end

    def mapped2(enum)
      enum.map { 2 }
    end
  end
end
simplecov_sample_spec.rb
require 'spec_helper'

describe SimplecovSample do
  subject { SimplecovSample::Sample.new("hoge") }

  describe "#test1" do
    it "" do
      expect(subject.test1()).to eq "test1"
    end
  end

  describe "#test_proc1" do
    it "" do
      expect(subject.test_proc1()).to eq "test_proc1"
    end
  end

  describe "#test4" do
    it "" do
      expect(subject.test4()).to eq "test4"
    end
  end

  describe "#name" do
    it "" do
      expect(subject.name).to eq "hoge"
    end
  end

  describe "#name2" do
    it "" do
      expect(subject.name2(true)).to eq "HOGE"
    end
  end

  describe "#name3" do
    it "" do
      expect(subject.name3(false)).to eq "hoge"
    end
  end

  describe "#name4" do
    it "" do
      expect(subject.name4(2)).to eq "hogehoge"
    end
  end

  describe "#mapped" do
    it "" do
      expect(subject.mapped([])).to eq []
    end
  end

  describe "#mapped2" do
    it "" do
      expect(subject.mapped2([])).to eq []
    end
  end
end

命令網羅(C0)として、不十分な点

  1. 引数が文字列のclass_evalでメソッドを定義したとき、そのメソッドがテスト対象に含まれない (test1のケース)

そもそも、テストがカバーすべきメソッドに含まれないということです。

  1. eachのblock内でdefine_methodを用いてメソッドを定義したとき、いずれかのメソッドをテストすれば、他のメソッドもテストしたことになってしまう (test4のケース)

テストがカバーすべきメソッド数が実際より少なくなってしまいます。

分岐網羅(C1)として、不十分な点

  1. 三項演算子のうち、片方をtestしていれば、もう一方もテストしたことになってしまう (name2のケース)

テストがカバーすべき分岐数が実際より少なくなってしまいます。

  1. if修飾子を使うと、falseのときでもテストしたことになってしまう (name3のケース)

こちらも、テストがカバーすべき分岐数が実際より少なくなってしまいます。

blockのカバレッジについて

each等の引数のblockをワンラインで書くと、eachが一度も回らなくともテストしたことになってしまいます (mapped2のケース)。

rubyのcoverageライブラリ

SimpleCov gemはrubyの標準ライブラリであるcoverageを利用しています。このライブラリを追っていくと、rb_iseq_tつまりRuby の Virtual Machine のコンパイル済みの命令シーケンスが持っているline番号を使っていることが分かります。このことから、上記の問題はSimpleCovの問題ではなく、coverageライブラリの問題だと考えられます。

雑感

カバレッジは参考になる尺度の一つくらいに捉えて、カバレッジにとらわれすぎずにテストを楽しめるのがいいですね。

19
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?