この記事は https://www.wantedly.com/companies/wantedly/post_articles/437514 の続きになっています。まだ前回の記事を読んでいない方はそちらを先に読むといいかもしれません。
今回は Ruby 3.2 の新機能を使って今まで取れなかった Rails View のコードカバレッジを測定したので、その導入方法と測定結果を紹介します。また前回は Ruby 標準の Coverage クラスを直接利用していましたが今回は simplecov を利用して検証を進めていきます。
ここまでのおさらい 本題に入る前に https://www.wantedly.com/companies/wantedly/post_articles/437514 の内容をざっと復習しておきましょう。 前記事 の要点はざっと以下の通りです。
Ruby 3.2 で eval 内のコードカバレッジが測定できるようになった ERBやHAML は内部で eval を利用しているのでこれらのコードカバレッジも測定できるかも? 前回は eval 内と ERB テンプレート内の Code Coverage 測定ができることを確かめた この記事では前回に続き、より実用的なユースケースとして Rails アプリケーションの View Template で SimpleCov を用いたカバレッジ測定が出来ることを確認します。
eval 内の Code Coverage が取れること ( 前記事 ) ERB テンプレート内の Code Coverage が取れること ( 前記事 ) Rails アプリケーションで View の Code Coverage が取れること 👈 (本記事) SimpleCov + ERB 👈 (本記事) SimpleCov + HAML👈 (本記事) SimpleCov について
SimpleCov は Ruby 向けのコードカバレッジ解析ツールです。現在 Ruby でコードカバレッジを測定する際は Ruby 標準の Coverage クラスではなくこのライブラリを利用することがほとんどでしょう。
SimpleCov は内部で Ruby 標準の Coverage クラスを利用しながら、外部にはCoverageクラスよりもシンプルなAPI、HTML形式での Visualization を提供しています。
例えばテスト実行前に以下のようなコードをロードしてあげれば
require 'simplecov'
SimpleCov.start
このような HTML 形式のコードカバレッジの測定結果を確認することができます。
https://github.com/simplecov-ruby/simplecov#example-output より
また、現在 SimpleCov(Coverage) は ERB や HAML のようなテンプレートエンジンのコードカバレッジ解析は行えません。このことは SimpleCov の README.md 冒頭でも言及されています。
SimpleCov/Coverage track covered ruby code, gathering coverage for common templating solutions like erb, slim and haml is not supported. これは
各種テンプレートエンジンが内部で eval を利用している Coverage クラスは eval 内のコードカバレッジは取れない ことに起因しています( 前記事 の内容)。
これらの事実に対し、本記事では Ruby 3.2 の力を使って ERB, HAML テンプレートのコードカバレッジを測定していこうと思います。
SimpleCov で Rails View のカバレッジを測定する 導入方法 SimpleCov 自体の導入は済んでいる前提で進めます。
前回の記事 でも触れたように、Coverage クラスで eval (view tempalte) 内のコードカバレッジをとるには新たに追加された eval mode を有効にする必要があります。
Coverage.start(lines: true, eval: true)
例に漏れず内部で Coverage クラスを利用している SimpleCov もこの eval mode を有効にしてあげる必要があるのですが、SimpleCov は Coverage クラスのモードを直接触れるAPIを提供していないので下記のようなモンキーパッチを当てましょう。
require "coverage"
module EnableEvalCoverageModePatch
def start(*args)
*args[0][:eval] = true # 常に eval mode が有効になる
super(*args)
end
end
Coverage.singleton_class.prepend(EnableEvalCoverageModePatch)
少々力技ですがこれで SimpleCov でも eval モードを利用できるようになります。
ただし SimpleCov が eval モードの有効化を直接提供していないということはまだライブラリが eval mode を完全にサポートしていないということです。この記事の後半で触れますがこの方法だと一部カバレッジ解析が上手くいっていない箇所があるので注意です。
ERB 検証 それでは ERB から Rails View のコードカバレッジを測定していきましょう。利用したコードは以下の通りです。
# View(ERB)
<p>
<% if @text.present? %>
<%= @text %>
<% else %>
nothing
<% end %>
</p>
# Test(RSpec)
require "rails_helper"
require "coverage"
describe "sample/index.html.erb", type: :view do
it do
@text = "test"
render template: "sample/index", formats: [:html]
end
end
# spec/rails_helper.rb
require 'simplecov'
require "coverage"
module EnableEvalCoverageModePatch
def start(*args)
*args[0][:eval] = true
super(*args)
end
end
Coverage.singleton_class.prepend(EnableEvalCoverageModePatch)
SimpleCov.start
これらのコードでテストを実行すると、以下のような解析結果が得られました。
今まで取れなかった View ファイルのカバレッジ結果を取得できています。また、実行された行・実行されなかった行・測定対象外の行が正しく表示されていることも分かります。 このように ERB テンプレートでは eval mode の有効化だけで満足いく解析結果が得られました。
HAML 検証 続いて ERB 同様に HAML でも検証を進めていきます。ERB とコードが変わるところのみ記載します。
# View(HAML), 他のコードはERBの検証と同じ
%p
- if @text.present?
= @text
- else
nothing
それではこの View に対してテストを実行してみましょう。得られた解析結果は以下の通りです。
ERB 同様に今まで取れなかった index.html.haml
に対応する解析結果が表示されていることが分かります。 しかし ERB と比べ解析結果が少しおかしいことになっています。本来であれば 4行目の - else
は Coverage 測定の対象外のはずです。 しかし実際には4行目は実行されたことになっています。何が起こっているのでしょうか。この記事ではこの解析の挙動についてもう少し深掘りしてみることにします。
一旦検証のまとめ HAML の解析結果について深掘りする前に、一旦ここまでの検証をまとめます。
検証結果:
ERB ✅ SimpleCov で Rails View ファイルのコードカバレッジ測定ができた ✅ 実行された行・実行されなかった行・測定対象外の行の判定が適切に行われた HAML ✅ SimpleCov で Rails View ファイルのコードカバレッジ測定ができた 🤔 実行された行・実行されなかった行・測定対象外の行の判定に問題があった ERB, HAML ともに最低限のカバレッジ測定はできていそうです。 ただしERB に比べて HAML の解析結果には一部懸念が残ります。ここからは「実行された行・実行されなかった行・測定対象外の行の判定」について詳しく見ていくことにします。
実行された行・実行されなかった行・測定対象外の行の判定 そもそも「実行された行・実行されなかった行・測定対象外の行」とはどういったものでしょうか。本題に入る前にある程度認識を揃えておきましょう。
https://github.com/simplecov-ruby/simplecov#example-output より
「実行された行・実行されなかった行・測定対象外の行」の違いは SimpleCov の解析結果を見ると分かりやすいです。SimpleCov の行表示はそれぞれ以下のような意味を持っています。
緑色の行 → コードが実行された (実行された行) 赤色の行 → 実行可能な行でコードが実行されなかった (実行されなかった行) 白色の行 → 測定不可能な行 (測定対象外の行) ここで肝になっているのは未実行の行が「実行されなかった行」と「測定対象外の行」の2つに分かれていることです。次の章で詳しく説明しますが、これらの分類はカバレッジ測定をより精度良くおこなうために存在しています。
カバレッジ測定の精度 ここでいうカバレッジ測定の精度は「カバレッジ測定全体の分母」とも言うことができます。解析結果に対して全体の分母が正しく設定されていないと以下のようなカバレッジ測定の数値は正確でなくなります。
例えばこれは極端な例ですが
このようなファイルがあったとき、たとえ実行可能な行が全て実行されてもカバレッジ測定時に測定対象外の行を正しく認識できないと測定結果は実際の値より低くなってしまいます。(本来は 100% になるべき)
このようにカバレッジ測定対象外の行を正しく認識することは、カバレッジ測定をより正確なものしたい際にとても重要になります。
測定対象外の行判別 では測定対象外の行判別はどのような方法で実現されているのでしょうか。
測定対象外の行の判別は意外と考えることが多くややこしくなっています。具体的には以下の3ケースを知る必要があります。
実行ファイル内のカバレッジ無効行の存在 未実行ファイルの存在 未実行ファイル内のカバレッジ無効行の存在
1. 実行ファイル内のカバレッジ無効行の存在 これは前章で紹介した「測定対象外の行」と同じものです。
このカバレッジ結果の白い行が「実行ファイル内のカバレッジ無効行」になります。
2. 未実行ファイルの存在 これは下の画像にあるような covered 0.00% なファイルのことです。
このファイルをカバレッジに含めるか含めないかでも全体のカバレッジ率 (covered %) が大きく変わってきます。
3. 未実行ファイル内のカバレッジ無効行の存在 これは 2. で得た未実行ファイルの中でさらに「どの行が測定対象外か」を調べたものです。 この情報まで調べるとコードベース全体の正確なカバレッジ測定結果を得ることができます。
SimpleCov の現実装 続いて、現在 SimpleCov がどうやってこれらの情報をRubyコードから集めているのか確認していきます。
ざっと現実装のドキュメント、ソースコードを読んだ感じだと以下の通りでした。
(1) 実行ファイル内のカバレッジ無効行の存在 (2) 未実行ファイルの存在 (3) 未実行ファイル内のカバレッジ無効行の存在 SimpleCov が SImpleCov::SimulateCoverage クラスで判定する SimpleCov 実装 ではこれらの実装のうち ERB/HAML では何が動作して、何が動作しなかったのでしょうか。一旦まとめてみましょう。
今回の検証では ERB/HAML それぞれで何が動作した(しなかった)のか ERB/HAML それぞれの挙動について、本記事で行った検証結果(+@ 追加検証、後述)から分かっている挙動は以下の通りです。
ERB
(1) 実行ファイル内のカバレッジ無効行の存在 (2) 未実行ファイルの存在 (3) 未実行ファイル内のカバレッジ無効行の存在 HAML
(1) 実行ファイル内のカバレッジ無効行の存在 (2) 未実行ファイルの存在 (3) 未実行ファイル内のカバレッジ無効行の存在 (1) については ERB は動作しており、HAML についても一部を除き大体動作しています。 一方で (2), (3) では今回の検証ではどちらのテンプレートも動作を確認できませんでした。(2) については追加で検証を行ったので結果を載せておきます。
追加検証: (2) 未実行ファイルの検出 やったこと: 前章の ERB/HAML 検証それぞれで View ファイルを2つ用意 そのうち片方のみの View Spec を書いて SimpleCov でカバレッジ測定を行う 期待する結果: Spec を書いていない View ファイルが covered 0% で検出される 実際の結果: Spec を書いていない View ファイルは検出されなかった # View
app/views
└── sample
├── index.html.erb
└── show.html.erb
# Test(RSpec), index.html.erb のみテストする
require "rails_helper"
require "coverage"
describe "sample/index.html.erb", type: :view do
it do
@text = "test"
render template: "sample/index", formats: [:html]
end
end
# spec/rails_helper.rb
require 'simplecov'
require "coverage"
module EnableEvalCoverageModePatch
def start(*args)
*args[0][:eval] = true
super(*args)
end
end
Coverage.singleton_class.prepend(EnableEvalCoverageModePatch)
SimpleCov.start
今後の展望/まとめ Rails View でも完全なカバレッジ測定を行いたい ここまでの検証・調査でRails Viewファイル (ERB/HAML) のカバレッジ測定は 最低限動きはするが完全ではない ことが分かりました。また、前章で説明した「(2) 未実行ファイルの存在」「(3) 未実行ファイル内のカバレッジ無効行の存在」の検出を Rails View ファイルでも動かせるようにするのは少し長くなりそうなのでまた別のタイミングで挑戦してみようと思います。
完全とはいきませんが、この記事で紹介した SimpleCov + eval mode の導入方法で Rails View の大体のカバレッジは測れるようになったので実際に手元で ERB/HAML のカバレッジを測定したい時の参考になれば幸いです。