投稿に寄せて
本記事は、Railsエンジニアの登竜門である Railsチュートリアル
にて紹介されている
テストフレームワークである Minitest
を RSpec
に書き直していきます。
おそらく、Railsエンジニアを目指す方々が実際の業務で扱うテストフレームワークは
Minitest
に比べて、 RSpec
の方が断然多いのではないでしょうか!
このことからもRSpecを学ぶメリットは十分にあると思います。
本記事が、Rails勉強中の方の参考になれば幸いです。
概要
本記事はRailsチュートリアル(Rails 5.1)の第3章以降の本格的アプリ開発の部分を対象として、
MinitestからRSpecへのリファクタリングをおこなっていきます。
前提
Railsチュートリアル第3章と同じ条件で rails new
したアプリの土台がすでにあること
https://railstutorial.jp/chapters/static_pages?version=5.1#sec-slightly_dynamic_pages
RSpecで開発をする準備
ここでRSpecでテストを書いていく前にRSpecのセットアップをしていきましょう!
必要Gemリスト
gem 'rspec-rails'
gem 'spring-commands-rspec'
gem 'factory_bot_rails'
gem 'faker'
gem 'capybara'
gem 'database_cleaner'
gem 'launchy'
gem "selenium-webdriver"
各Gemの解説
rspec-rails
RSpecを使用するために必要なgem。
spring-commands-rspec
bin/rspecのコマンドを実行するときに必要になるもの。
rspecはbin/コマンドをつけることでSpringというRailsに組み込まれているアプリを起動させて処理を高速化出来る。
rspec自体はrspecと打ち込むだけでも起動可能。
factory_bot_rails
テストの際に使用するデータを作成するためのもの。
faker
実在しそうな名前でダミーデータを作成するためのもの。
capybara
アプリケーション操作をRubyで設定して、ユーザがアプリケーションを使っているかのようにページを遷移させて、不具合検証するためのもの。主に画面に関わる結合テストで使用する。
database_cleaner
逐一テストデータを削除するためのもの。
launchy
Capybaraでテスト中に、現在どのページを開いているのか確認するためのもの。
selenium-webdriver
複数テストの並行実行用。
実際のGemfile
source 'https://rubygems.org'
gem 'rails', '5.1.4'
gem 'puma', '3.9.1'
gem 'sass-rails', '5.0.6'
gem 'uglifier', '3.2.0'
gem 'coffee-rails', '4.2.2'
gem 'jquery-rails', '4.3.1'
gem 'turbolinks', '5.0.1'
gem 'jbuilder', '2.7.0'
group :development, :test do
gem 'sqlite3', '1.3.13'
gem 'byebug', '9.0.6', platform: :mri
gem 'spring-commands-rspec'
end
group :development do
gem 'web-console', '3.5.1'
gem 'listen', '3.1.5'
gem 'spring', '2.0.2'
gem 'spring-watcher-listen', '2.0.1'
gem 'pry-rails'
gem 'pry-byebug'
gem 'pry-doc'
end
group :test do
gem 'rails-controller-testing', '1.0.2'
gem 'minitest-reporters', '1.1.14'
gem 'guard', '2.13.0'
gem 'guard-minitest', '2.4.4'
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'faker'
gem 'capybara', '~> 2.13'
gem 'database_cleaner'
gem 'launchy'
gem 'selenium-webdriver'
end
group :production do
gem 'pg', '0.20.0'
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
下準備
- とりあえず bundle install
bundle install --path vendor/bundle
- bin/rspecファイルを生成
bundle exec spring binstub rspec
- RSpecの設定ファイル群を生成
rails generate rspec:install
- .rspecに以下の項目を追記して、テスト結果出力をドキュメント形式に変更する
<中略>
--format documentation
- application.rbを編集して、無駄なファイルが生成されないようにする
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module RailsTutorial514
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.1
config.generators do |g|
g.test_framework :rspec,
fixtures: true,
view_specs: false,
helper_specs: false,
routing_specs: false,
controller_specs: true,
request_specs: false
g.fixture_replacement :factory_bot, dir: "spec/factories"
end
end
end
- RSpecを実行出来るかコマンドで確認
$ bin/rspec
No examples found.
Finished in 0.00018 seconds (files took 0.06751 seconds to load)
0 examples, 0 failures
準備が出来たら実際にやっていこう
第3章の実装は下記のRailsチュートリアルの通り実装していく前提で進めます。
ここではテスト以外の実装には触れません。
https://railstutorial.jp/chapters/static_pages?version=5.1#cha-static_pages
3.3.1 最初のテスト
※ Railsチュートリアルの項目3.3.1を参照
テストすべき項目
- Home画面が正常に表示されること
- Help画面が正常に表示されること
Minitestで書いた場合
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
def setup
@base_title = "Ruby on Rails Tutorial Sample App"
end
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
end
```
### RSpecで書いた場合(画面表示周りのRSpecであるFeatureSpecで書く)
#### FeatureSpec(フィーチャースペック)の説明
FeatureSpecとはRailsのテストフレームワークのRSpecで行われるテストの種類の1つで、ブラウザ上の操作をシミュレートして実行結果を検証するテストのことです。
一般的には「統合テスト」や「エンドツーエンドテスト(E2Eテスト)」といった名称で呼ばれています。
ざっくりイメージで言うと、モデルとコントローラーがうまく一緒に動作することで表示も正しいものであることを検証するテストという理解で良いと思います!
以下は、今回Minitestから書き直したファイルです。
```/spec/features/static_pages_spec.rb
require 'rails_helper'
describe 'Home' do
specify '画面の表示' do
visit '/static_pages/home'
expect(page).to have_css('h1', text: 'Sample App')
end
end
describe 'Help' do
specify '画面の表示' do
visit '/static_pages/help'
expect(page).to have_css('h1', text: 'Help')
end
end
```
#### テストコードの補足
>`describe '〇〇' do` の〇〇の部分にテストしたい対象を記載する
>`specify '〇〇' do` の部分にテストしたい内容の詳細を記載する
>`visit 'URL'` 'URL'の部分に検証したい画面のURLを記載する
>`expect(page).to` はvisitしたカレントページを対象とするという意味
>`have_css` はテストの検証したい内容を計るためのメソッドで「マッチャ」(マッチを計るため)と呼ばれるものの1つです。
>`have_css('h1', text: 'Help')` で HTMLのh1要素の内容が `Help` かどうかを検証している
>`expect(page).to have_css('h1', text: 'Help')` でカレントページはh1要素のテキストとしてHelpを持っているかを検証している
#### 実行コマンド(このコマンドでRSpecのテストファイルに記載したテストを実行出来る)
```ruby
bundle exec rspec spec/features/static_pages_spec.rb
```
#### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222024/40d9f094-5e68-700a-e6d1-889dce94a100.png)
## 3.3.2 RED 〜 3.3.3 GREENまで
#### テストすべき項目
- Home画面が正常に表示されること
- Help画面が正常に表示されること
- About画面が正常に表示されること
#### Minitestで書いた場合
````test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
def setup
@base_title = "Ruby on Rails Tutorial Sample App"
end
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
test "should get about" do
get static_pages_about_url
assert_response :success
end
end
```
#### RSpecで書いた場合(画面表示周りのRSpecであるFeatureSpecで書く)
```/spec/features/static_pages_spec.rb
require 'rails_helper'
describe 'home' do
specify '画面の表示' do
visit '/static_pages/home'
expect(page).to have_css('h1', text: 'Sample App')
end
end
describe 'help' do
specify '画面の表示' do
visit '/static_pages/help'
expect(page).to have_css('h1', text: 'Help')
end
end
describe 'about' do
specify '画面の表示' do
visit '/static_pages/about'
expect(page).to have_css('h1', text: 'About')
end
end
```
```routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
root 'application#hello'
end
```
#### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222024/4538df34-30fa-3cbe-4983-66750f5ec34f.png)
## 3.4 少しだけ動的なページ
レイアウトファイル(Railsアプリの基盤となるテンプレート)の役割をよりわかりやすく説明するために、最初に レイアウトファイルの`application.html.erb` の無効化をする
```ruby
mv app/views/layouts/application.html.erb layout_file
```
## 3.4.1 タイトルをテストする(Red) 〜 3.4.2 タイトルを追加する(Green)
#### テストすべき項目
- Home画面タイトル "Home | Ruby on Rails Tutorial Sample App" が正常に表示されること
- Help画面タイトル "Help | Ruby on Rails Tutorial Sample App" が正常に表示されること
- About画面タイトル "About | Ruby on Rails Tutorial Sample App" が正常に表示されること
#### Minitestで書いた場合
```test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
```
#### RSpecで書いた場合(画面表示周りのRSpecであるFeatureSpecで書く)
- ここでテストの内容もタイトルの内容を検証するように変更
```/spec/features/static_pages_spec.rb
require 'rails_helper'
describe 'Home' do
specify 'タイトル内容の表示' do
visit '/static_pages/home'
expect(page).to have_title 'Home | Ruby on Rails Tutorial Sample App'
end
end
describe 'Help' do
specify 'タイトル内容の表示' do
visit '/static_pages/help'
expect(page).to have_title 'Help | Ruby on Rails Tutorial Sample App'
end
end
describe 'About' do
specify 'タイトル内容の表示' do
visit '/static_pages/about'
expect(page).to have_title 'About | Ruby on Rails Tutorial Sample App'
end
end
```
#### テストコードの補足
>`have_title` もテストの検証したい内容を計るためのメソッドで「マッチャ」(マッチを計るため)と呼ばれるものの1つです。
## 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222024/cc869c04-855e-0390-1531-8fdeefd5f9b1.png)
## 3.4.4 ルーティングの設定
### ルーティングの変更
`static_pages#home`を root( `/` )のルーティングに変更
```config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
```
#### Minitestで書いた場合
```test/controllers/static_pages_controller_test.rb
require 'test_helper'
# rootのURLにアクセスするテストを追加
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get root" do
get FILL_IN
assert_response FILL_IN
end
<中略>
end
```
#### RSpecで書いた場合(FeatureSpec)
```/spec/features/static_pages_spec.rb
require 'rails_helper'
# Home画面をrootとしてそこにアクセスするテストに修正する
describe 'Home' do
specify 'タイトル内容の表示' do
visit '/' # ここを変更
expect(page).to have_title 'Home | Ruby on Rails Tutorial Sample App'
end
end
<中略>
```
## 実行結果
- Home画面をrootに変わったテストも通り、既存のテストも通ることを確認
![image.png](https://qiita-image-store.s3.amazonaws.com/0/222024/7833a938-1980-90f0-54e9-a1e072f6fd43.png)
## 最後に
いかがだったでしょうか!
自分の周りでRSpecを慣れていきたい!もっと知りたい!!という声をよく聞きます。
実を言うと自分も普段Railsエンジニアをしているものの、RSpecをイチからセットアップしたり、なにも無いところからテストをゴリゴリ書いていくことってあんまりないんですよね...
(既存のコードを参考に追加のテストを書いてしまうこともある...)
そういった背景もあり、イチからテストのセットアップをしてテストコードを書く練習として、RailsチュートリアルのMinitestをRSpecへ翻訳させて頂きました。
時間を見つけて、第3章以降も翻訳を試みてみます。
初心者でRSpecをこれから練習したい!という方の良い回答サンプルになるような続編を頑張って書こうと思うので、続きも購読のほど、宜しくお願いします。
## 追記
※ Rails5.1以降ではFeatureSpecに代わって、SystemSpecというFeatureSpecのテストが推奨されるようになっています。普段の業務で使っていることから、当たり前のようにFeatureSpecで書いてしまいましたが、書いた後で気づきました...