“まだRails 4.2で消耗しているあなた私へ・・・”
SystemTestCaseの間違った(けど役に立つ)使い方
Rails 5.1から導入されたSystemTestCaseはE2Eテストのためのもの、と思われがちだが、
describe 'jQueryがふんだんに使われた検索結果ページ' do
before {
FactoryBot.create(:job_offer, title: '優良案件です')
FactoryBot.create(:job_offer, title: 'かせげるよ♪')
JobOffer.__elaseicsearch__.import
}
context 'AI判定がOFFのとき' do
before {
allow_any_instance_of(IntelligentFilter).to receive(:filter).and_return(false)
}
it '検索結果ページに2件とも仕事が表示されること' do
visit '/job_offers/search'
find("li.search_result", wait: 2) # 検索結果がJSで非同期描画されるまで待つ
expect(page).to have_content("優良案件です")
expect(page).to have_content("かせげるよ♪")
end
end
context 'AI判定で怪しい仕事が弾かれるとき' do
before {
allow_any_instance_of(IntelligentFilter).to receive(:filter) do |instance, job_offer|
job_offer.title.include?("♪") # 音符を含んでいる仕事は弾く
end
}
it '検索結果ページに優良案件だけが表示されること' do
visit '/job_offers/search'
find("li.search_result", wait: 2) # 検索結果がJSで非同期描画されるまで待つ
expect(page).to have_content("優良案件です")
expect(page).not_to have_content("かせげるよ♪")
end
end
end
上記のように、
- FactoryBotを使って疑似データを作成したり
- rspec-mockを使ってモック化したり
しながら、JSがフル動作する画面を試験 (ChromeやFirefoxを裏で動作する試験)させることができる。
明らかにおかしい使い方ではあるが、「jQueryをふんだんに使った複雑な画面のテストを、なんとかして書きたい・・・」みたいなときにはこれが結構役に立つ。
こんなことあるよね?
たとえばログイン後の挙動を見たいページが有ったとしよう。
def login(username, password)
fill_in("username", with: username)
fill_in("password", with: password)
find('#login').click
end
たとえば上記のログインの補助メソッドを作って、パターン網羅の試験を書いていたとしよう。
試験の関心事はログイン後の挙動にあるのに、ログイン画面のDOM構造が変わった時に全部のspecが一斉に落ちてしまう。
context '非ログイン状態でアクセス' do
before {
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(nil)
}
# いろいろ試験
end
context 'ログイン状態でログインしてアクセス' do
let(:current_user) { FactoryBot.create(:user) }
before {
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(current_user)
}
# いろいろ試験
end
context '仕事管理者でログインしてアクセス' do
let(:admin_user) { FactoryBot.create(:admin_user) }
before {
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(admin_user)
}
# いろいろ試験
end
context '退会済みユーザでログインしてアクセス' do
let(:inactivated_user) { FactoryBot.create(:user, :inactivated) }
before {
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(inactivated_user)
}
# いろいろ試験
end
「どんなログイン画面であれ、とりあえずログインできていればいいんだ!!」という場合には上記のようにモック化してしまえば圧倒的に効率的に試験が書けるし、壊れにくい試験になる。
このように、
- FactoryBotが使えることで、いろんなパターンのデータを全ての手順を物語のように書く必要がない
- 単体テストで品質保証ができているところは積極的にモック化することでテストスコープを絞った試験が書ける
という点が良い。
でもSystemTestCaseってRails 5.1からしか使えないんだよね・・・
Rails 6がもうすぐリリースされそうな現在でも、Rails 4系を使っている会社は少なからずあると思う。
・パターン分岐が多くて、手動試験が大変
・なんだけど、JSがふんだんに使われていて、Specは書けない
のような葛藤と格闘していることだろう。
→Rails 5.1になったらSystemTestCaseでうまいことSpec書けるようになる?
→でもRails 4→5をアップデートするには試験がががが・・・・
SystemTestCaseってRails 4にバックポートできないの??
結論から言うと、たぶんできる。
完璧に移植は無理かもしれないけど、ほどほどに動く状態にはできる。
- https://github.com/rails/rails/pull/26703 : SystemTestCaseなどの定義
- https://github.com/rails/rails/pull/28083 : RSpecとRailsサーバーでDBコネクションのスレッドを共有する
の2つをRails 4.2にバックポート(モンキーパッチ)できれば、Rails 4.2でもsystem specは動かせる。
やってみた
https://bitbucket.org/iwaki-i3/rails4_rspec_system_spec_mock_playground/src/master/backported_system_test_case/
SystemTestCaseなどの定義
https://github.com/rails/rails/pull/26703 を参考に
|- action_dispatch
|- system_test_case.rb
|- system_testing/
をRails 5.2以降のソースからコピー。
RSpecとRailsサーバーでDBコネクションのスレッドを共有する
https://github.com/rails/rails/pull/28083 をRails 4.2ベースのコードでモンキーパッチ作って、あてる。
ローカルGemとして取り込む
bundle gem backported_system_test_case
でGemのテンプレを作ったところに、先のSystemTestCase類のファイルとモンキーパッチを放り込み、
サービス本体のGemfileで
group :test do
# 〜いろいろ〜
gem 'backported_system_test_case', path: 'backported_system_test_case' # 追記
end
のように1行追加して取り込む。
まとめ
「jQueryをふんだんに使っていて試験が書けない・・・」ってなってるところは、SystemTestCaseを無理やりバックポートして、効率よく試験を書いて、安心感もって早くRails 5, Rails 6に上げよう。