pockestrap

Programmer's memo

Itamaeのテストを壊してしまっていた話

本日、Itamae v1.10.4がリリースされました。

このリリースには私のPull Requestがいくつか含まれています。 この記事ではそのうちの1つのPull Requestについてお話しようと思います。

そのPull Requestでは、過去に私がItamaeのリポジトリに対してやらかした失敗を修正しています。 この記事を読むことで、同様のやらかしを未然に防げるようになれば嬉しいです。

問題: テストが走っていない

github.com

該当のPull Requestがこれです。 要約すると、「走るべきテストが走らなくなっていたから直した」というPull Requestです。

原因

原因は、RSpecのパターンの表記ミスでした。 RSpecではpatternにglobで実行するテストファイルを指定できます。

私はItamaeに#281結合テストを1つ追加しました。 その際、個々の結合テストは別のspecファイルを走らせる必要がありました。

そのためRakefile内で次のようにパターンを指定しました。

RSpec::Core::RakeTask.new(:spec) do |t|
  t.pattern = "spec/integration/[default|docker]_spec.rb"
end

いかにももっともらしい記述に見えますが、このパターンは誤っています。 ここではspec/integration/default_spec.rbspec/integration/docker_spec.rbの両方を読み込むことを期待していますが、globのunionパターンは正しくは{default,docker}のように書く必要があります。

relishapp.com

このパターンの指定を間違えていたためにテストファイルが1件も見つからず、テストが実行されていませんでした。 また1件もテストが走っていない場合、当然ながらテストは1件も失敗しません。 そのためこのテストは「成功」として扱われ、テストが走っていないことの発見が遅れてしまいました。

対策

このような問題に対して「次から気をつけよう」と思っているだけではあまり意味がありません。 プログラミングをする上で気をつけたいことは無限にありますし、全てに対して完全な注意を払っていてはいつまで経っても目的のプログラムは完成しないでしょう。

そのため、より仕組み化された対策の仕方をいくつか提案します。

fail_if_no_examples オプションを有効にする

relishapp.com

fail_if_no_examplesオプションを有効にすると、今回のようなケースでexit statusが非0になってくれます。 そのため仮にパターンを間違えたとしても、すぐに問題に気がつくことができます。

実際に試してみましょう。

$ rspec --init
  create   .rspec
  create   spec/spec_helper.rb

# 設定なしの場合
$ rspec --pattern no-spec-pattern
No examples found.

Finished in 0.00019 seconds (files took 0.07372 seconds to load)
0 examples, 0 failures

# exit statusが0で成功扱いになっている。
$ echo $?
0


# fail_if_no_examplesを設定した場合
$ echo 'RSpec.configure { |c| c.fail_if_no_examples = true }' > spec/spec_helper.rb

$ rspec --pattern no-spec-pattern
No examples found.

Finished in 0.00014 seconds (files took 0.03766 seconds to load)
0 examples, 0 failures

# exit statusが1で失敗になっている。
$ echo $?
1

このようにfail_if_no_examplesを設定すると、「テストケースがない場合」にexit statusを非0にしてくれます。

なお、この設定をする際には.rspecファイルに--require spec_helperと書いておくことが推奨されています。 もしこれが書かれていない場合、なにもテストファイルをロードしなければspec_helperがロードされることもありません。つまりせっかくの設定が無視されてしまいます。

# fail_if_no_examplesを設定している時に、.rspecを消した場合
$ rm .rspec
$ rspec --pattern no-spec-pattern
No examples found.

Finished in 0.00014 seconds (files took 0.03715 seconds to load)
0 examples, 0 failures

$ echo $?
0

このオプションを使うことで、今回のようなミスを確実に防ぐことができます。

ただし、Itamaeの場合は少々特殊であるため、このオプションを単純に有効にできません。 Itamaeは大きく分けて2つのテストの方法を持っていて、両方でRSpecを使っています。

  • 普通のユニットテスト
  • Itamaeを実行したDockerコンテナに対してServerspecを実行するテスト

そしてこれらのテスト間では、spec_helper.rbは別のものを使用しています。 そのため先程紹介したように.rspecにrequireを書くには、それぞれのテストに対して別の方法を取る必要があるでしょう。

そのためfail_if_no_examplesをItamaeに導入するには少し手間が必要ですが、やればできるぐらいの問題だと思います。 興味がある方は導入するPull Requestを作ってみてはいかがでしょうか。

コードカバレッジを監視する

また、もう一つの対策としてコードカバレッジを監視することが考えられます。

今回のようにテストファイルが丸々無視されている場合、コードカバレッジが大きく下がることが予測されます。 そのためコードカバレッジの変化を監視していれば異変に気が付き、問題を事前に見つけることができたでしょう。

とはいえこれまたItamaeの事例では導入が難しい解決方法の1つです。 今回発生した問題は、Itamaeの2つのテストのうちServerspecを使用している方のテストで起こっていました。 つまり今回の問題は「Itamaeでプロビジョニングは完了したが、その結果が正当かServerspecでテストされていなかった」という問題です。

このケースではItamaeのコードは実行されています。つまりItamaeのライブラリのコードのカバレッジを取るだけでは、特に変化が現れなかったに違いありません。

そのため、Itamaeでコードカバレッジの変化を監視して今回の問題を見つけるためには、Serverspecのコードのカバレッジを取る必要があるでしょう。


この記事では、Itamaeで私がやらかしてしまったミスの解説と、その回避方法について解説しました。 みなさんのプロジェクトでこのようなミスを起こしてしまわないためにも、対策してみてはいかがでしょうか。