追記 2017-12-25
Ruby2.5.0で分岐カバレッジのための情報が追加されるみたいなので、それをベースにしたカバレッジツールが出てきそうですね。書いて数日ですが、この記事の内容も古くなりそうなので気をつけてください。
Add branch coverage https://bugs.ruby-lang.org/issues/13901
トレタ Advent Calendar 2017の21日目記事です。
トレタでは、SimpleCov(https://github.com/colszowka/simplecov) を使って、
Railsアプのコードカバレッジを計測しています。
そのときに気をつけていることを、いくつかつか紹介したいと思います。
行単位でしか判断できない
コードカバレッジにはいくつか種類がありますが、SimpleCovで計測できるのは、命令網羅(C0)と呼ばれる
行単位でコードが実行されたかを確認するものです。(これはRubyVMから実行行を取得し、それをもとに作られているためです)
C0カバレッジは比較的網羅率を上げやすいかんたんなものですが、逆にそれだけでは問題に気づけないケースがでてきます。
3項演算子
def div(x)
1 / (x ? 1 : 0)
end
xがtrueな値のテストだけが書いてあるとして、カバレッジを計測すると100%になります。
このケースでfalseを入れると例外が起きてしまいます。あえてifで書いてみると、そのパスが通らないことがはっきりわかります。
後置きのif/unless
a = 1
a = 2 if x == true
これも3項演算子と同じで、xがどういう値であれ、この行を通ってしまうので、xがtrueのケースを書かなくても、この行が通ったことになります。a = 2の部分を通っていないケースに気をつけましょう。
ブロック
sum = some_arr.select { |i| i > 0 }.reduce { |r, i| p i ; r + i }
このケースでは、some_arrに0以下の値しかない場合に、selectで空配列になってしまうので、そのあとのreduceはなにもしません。reduceに限らず、ブロックを使っているときに、実はそれが実行されないケースのテストしか書いていないというのに気をつけましょう。
たとえばsum([-1, 0])みたいなテストだけしても100%になります。
改行をいれても変わりません。行単位といっても、物理的な改行ではなくてRubyのプログラムとして一行単位なためです。
doブロックにしてみると、行が変わるのでテストが通っていないことがわかります。
メタプログラミング
メタプログラミングを使ってメソッドやクラスを動的に生成していると、
その部分に対してテストが通っているかをカバレッジで判断できないケースがが多くあります。
[0, 1, 2].each do |i|
define_method "m#{i}" do
1 / i
end
end
m0, m1, m2が定義されますが、どれか一つでも通せはこの行を通ったことになります。
また文字列からevalするケースではカバレッジは無力です。
まとめ
思いついたケースをまとめただけなので、まあまだ他にも気をつけるケースはあると思います。
それから、カバレッジのために3項演算子や後置ifやメタプログラミング使うな、という話ではなくて、
使っているカバレッジの性質を理解して、そういう部分にはテストを書くときに注意するとか、
より危なそうなケースでは、あえて冗長にしてもいいし、そのへんは各自で判断してやっていくのがいいと思っています。
もっと強力なカバレッジツールがあれば、こういうことに気をつけなくてもいいようになる可能性もありますが、現状は見当たらないので(あればおしえてください)、テストが意図通りに動作を確認できているかの一つの道具/指標として、SimpleCov使っていきましょう。