RSpecで一部のテストを特定のタイミングでだけ有効にする

もう RSpec 忘れました。対象は

  • rspec-core 3.13

です。

やりたいこと

  • heavy test を手元の通常の開発サイクルから除外したい

重いテストはきらいです。重いテストはきらいです。重くならざるを得ないのは分かります。でも自分が今書きたいのは重くないテストなんで、邪魔せんといて。

patternではない

Method: RSpec::Core::Configuration#exclude_pattern — Documentation for rspec-core (3.13.0)

最初に思いついたのは

  • exclude_pattern

を使って特定のパターンにマッチするテストを除外するという方法だったんだけど、これはファイル名でマッチするので、ファイル名以外の任意の条件で除外するということはできない。

例えば同じパスの中に重いテストと重くないテストがあって、重くないテストは除外したくない場合には役に立たない。

filter_run_*が正解っぽい

rspec-core/Filtering.md at main · rspec/rspec-core

rspec 実行時の filtering はいろんな書き方ができて、-t オプションなど実行方法もいくつもあるんだけど、そんな複雑なことはしたくない

  • 普段は除外されていてほしい
  • ただし、いざという時にはすべてテストしてほしい

これくらいが何も考えずに実現してくれたら嬉しい。いちいち起動オプションやフィルタ設定方法を調べたくない。そうすると、

  • 平時は config.filter_run_excluding を利用して動作しないように
  • 何らかの条件を満たしたらこの設定を反映しないように

すればよい。

ということは大枠は以下のような感じだ。

RSpec.configure do |config|
  unless <いざ>
    config.filter_run_excluding <重いやつ>
  end
end

例えば Ruby on Rails を利用していて以下のような js: true な System spec があったとする。

describe "SomeSystem", js: true do
end

この時、

  • js: true なテストについては Selenium 経由でブラウザを動かして重いので除外したい
  • ただし CI 上ではすべて実行したい

ということであれば以下のよう設定になる。

RSpec.configure do |config|
  if ENV["CI"] != "true"
    config.filter_run_excluding js: true
  end
end

これは多くの CI/CD サービスで自動的に環境変数 CItrue という文字列になることを利用して、逆にそれが設定されていない場合は js: true なテストを除外するという設定になっている。

filter_run_excludingをもう少し詳しく

Method: RSpec::Core::Configuration#filter_run_excluding — Documentation for rspec-core (3.13.0)

Adds key/value pairs to the exclusion_filter. If args includes any symbols that are not part of the hash, each symbol is treated as a key in the hash with the value true.

  • key/value pairs を受け付ける
  • もし Hash の一部じゃない Symbol が現れたらそれは true という値を持っているものとして扱われる

また、複数の設定を書きたければ複数の filter_run_excluding を並べるようだ。

filter_run_excluding は中で FilterManager を使って一つずつ @exclusions に追加していくように書かれている。

module RSpec
  module Core
    class Configuration
      def filter_run_excluding(*args)
        meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
        filter_manager.exclude_with_low_priority meta
        static_config_filter_manager.exclude_with_low_priority Metadata.deep_hash_dup(meta)
      end

なるほど。

module RSpec
  module Core
    class FilterManager
      attr_reader :exclusions, :inclusions

      def initialize
        @exclusions, @inclusions = FilterRules.build
      end
snip)
      def exclude_with_low_priority(*args)
        exclusions.add_with_low_priority(args.last)
      end

なるほどなるほど。

More