【Ruby/RSpec】特定の場合だけ実行する/しないテストを書く

2020年11月9日

こんにちは、しきゆらです。
今回はRSpecで特定の場合だけ動く・動かないようなテストを書く方法を知ったのでメモしておきます。

やりたいこと

ログイン周りのテストをCIで実行したくない

書いていたコードとしては、Seleniumを使ってWebサービスのテストを書いていました。
諸々のテストはいいんですが、ログイン周りのテストは2段階認証などがあるため、Waitをかけて人の手で操作していました。

このRSpecをCI上で動かそうとしたとき、ログイン周りだけは止めたい!
というのが一番初めにあったやりたいことでした。

特定のクラスを継承している場合は実行したくない

合わせて、コードの中で特定のクラスを継承している場合のみこのテストを実行したい、ということもありました。
RSpecはテストを共通化する仕組みがあるので、それを使ってテストコードを共通化していたのですが
一部共通化していたテストの中で、ちょっとまずい事態が出てきました。
上記に関連してSelenium周りなんですが、Seleniumを使うクラスとSeleniumを使わないクラスが出てきたことで共通化していたテストの一部がSeleniumを使わないクラスでは必要なくなるということが起きました。
共通化していた部分を分けてSelenium用の共通コードとSelenium使わないクラスの共通コードに分ければいいんでしょうが、両方とも分類的には同じくくりなので名前を分けるだけだと、名前が似てしまってごちゃっとしてしまいそうでした。

これを回避するために、共通化しているSpec側で実行されているクラスを判定して、必要ない場合は実行しないような世界を作りたかったんです。
こちらも併せて方法が分かったのでメモしておきます。


解決方法

ログイン周りのテストをCIで実行したくない

これに関しては、割と簡単に実現できました。
実行したくないSpecにタグをつけてあげればよいだけでした。
参考:--tag option – Command line – RSpec Core – RSpec – Relish | https://relishapp.com/rspec/rspec-core/v/3-5/docs/command-line/tag-option

簡単に言えば、Specに印をつけてあげることで、実行時にそこだけ実行したりはじいたりすることができます。
書き方も特に難しくなく、普通にSpecを書いたところにちょこっと追記してあげればよいだけです。

describe "hoge", run: true do # run: trueがタグ
  it {expect(hoge).to eq "hoge"}
end

こんな感じで、describeの記述の後にHashのように記載してあげればOK。
シンプルに:runという風にも記載できます。

このようにタグをつけてあげた後、ターミナルから以下のようにすると実行できます。

# タグが付いたもののみ実行
bundle exec rspec -t run # run: trueのもののみ実行

# タグが付いたもののみ除外
bundle exec rspec -t ~run # run: trueのものを除外

このタグは良しなに自分でつけられるものなので、わかりやすくつけてあげることができます。
私の場合は、ログイン周りのSpecにはlogin: trueとつけてあげていたので
CI実行時にrspec -t ~loginとして実行するように設定してあげることで、CI上ではログイン周りのSpecを動かないように設定できました。

特定のクラスを継承している場合のみ実行してほしい

これも調べてみたら解決方法がいくつか見つかりました。

それぞれに共通する仕組みとして、知っておきたいのは「ヘルパーメソッド」です。
Rspecはヘルパーメソッドは、Spec内で共通する処理をメソッドに切り出す仕組みです。
これを使うことで、Spec内で何度も出てくる記述に名前を付けて切り出すことが可能になります。
まぁ、RSpecといっても基本はRubyなので、普通にコードを書く時と考え方としては同じですね。

まず一つ目は、itに対して条件を付ける方法です。
参考:Conditional Filters – Filtering – RSpec Core – RSpec – Relish | https://relishapp.com/rspec/rspec-core/v/3-5/docs/filtering/conditional-filters
この方法は、条件がtrueの場合のみそのテストを実行するという形になっているので、特に実行時に指定したりする必要がないのがないため、引数の設定等をいじる必要がないので楽ちんです。
例としては以下のような感じ。

context "hogehoge" do
  # 日曜日のみ実行されるSpec
	it if: Date.today.sunday? do
	  ~~~~~~~~~~~~~~~
  end
end

上記の例は、日曜日の場合に実行されるSpecです。
これ自体に意味はありませんが、itに条件を指定して実行されるかどうかを制御することができるようです。
なお、例ではifですがunlessも使えます。

この条件式に良しなに条件を渡してあげればよいわけです。
今回であれば、特定のクラスを継承しているかどうかですね。
継承しているかどうかをヘルパーメソッドで定義してあげれば、if: inherited?のようにパッと見ただけでわかりやすくなりそうです。

もう一つの方法は、最初に提示したタグをつけるものです。
なお、この方法の場合はタグのtrue/falseを動的に定義するだけなので、実行時に引数としてタグを含む/除外するとうの指定が必要になります。

例としては、以下の通り。
なお、described_classに関しては、以前メモしてあるのでそちらもどうぞ。
参考:【RSpec】described_class

# ヘルパーメソッドを定義
def inherited?
  described_class < Hoge
end

class Hoge; end
class SubHoge < Hoge; end
 
RSpec.describe SubHoge do
  context "実行される", hoge: inherited? do
    puts "run!!"
  end
end
 
class Fuga; end
class SubFuga < Fuga; end
 
RSpec.describe SubFuga do
  context "実行されない", hoge: inherited? do
    puts "not run..."
  end
end

こんな形で、タグの条件にヘルパーメソッドを置いてあげればよいわけです。
あとは、前述の通り実行時にそのタグを含めるかどうかの指定をしてあげればいいだけ。
わかってしまえば、確かに難しいことはありません。

まとめ

今回は、RSpecの中で特定のテストを実行したくない、という状況を解決するための方法を調べてみました。
調べてみると、難しく考えすぎていたなぁという印象でした。

今回は、ここまで。
おわり

RSpec,Ruby

Posted by shikiyura