【Ruby on Railsチュートリアル】第3章 テスト開始

テストから始める

www.around50th-woman.me

静的なページとちょっと動的なページをやってそれにともなうテストってことのようです。

テストはいつどのように行うものなのか?

レガシーなエンジニアはまず一通りのコーディングをして単体テストをして結合テストをする流れなわけで。

去年か一昨年か?どこかの現場で机上の空論でコーディングをする前にテストケースを書かされた。それはあくまでも概要把握ってこともあったのだと思うのだけど。

なのでテストについては考え方から変えなきゃです。

テストのメリット

  1. テストが揃っていれば、機能停止に陥るような回帰バグ (Regression Bug: 以前のバグが再発したり機能の追加/変更に副作用が生じたりすること) を防止できる。

  2. テストが揃っていれば、コードを安全にリファクタリング (機能を変更せずにコードを改善すること) ができる。

  3. テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。

なるほどねぇ〜。

最初のテスト

rails generate controller実行時にテストファイルがちゃんと作成されている。

StaticPagesコントローラのデフォルトのテスト

test/controllers/static_pages_controller_test.rb

equire 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  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

このままパスすることを確認する。

$ rails test
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

テスト駆動開発のサイクル

  • 失敗するテストを最初に書く:RED
  • 次にアプリケーションのコードを書いて成功させる (パスさせる):GREEN
  • 必要ならリファクタリングする:REFACOR

Aboutページのテスト

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
  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

実行

$ rails test
NameError: undefined local variable or method `static_pages_about_url'

「AboutページへのURLが見つからない」ってことで、ルーティングファイルを修正する。

about用のルートを追加する
config/routes.rb

Rails.application.routes.draw do
  get  'static_pages/home'
  get  'static_pages/help'
  get  'static_pages/about' #←追加
  root 'application#hello'
end

実行

$ rails test
AbstractController::ActionNotFound:
The action 'about' could not be found for StaticPagesController

StaticPagesコントローラにaboutアクションがない」ってことで、コントローラファイルを修正する。

aboutアクションが追加されたStaticPagesコントローラ
app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end
 # ↓ 追加
  def about
  end
 # ↑
end

実行

$ rails test
ActionController::UnknownFormat: StaticPagesController#about
is missing a template for this request format and variant.

今度はテンプレートがないということで

$ touch app/views/static_pages/about.html.erb

touchコマンドは本来、ファイルやディレクトリのタイムスタンプだけを更新するためのコマンドなのですが、ファイルが存在しない場合には空ファイルを作成するという一種の副作用がある。クラウドIDEの場合は、ファイルツリーの更新をしないと表示されない。

Aboutページのコード
app/views/static_pages/about.html.erb

<h1>About</h1>
<p>
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  is a <a href="https://railstutorial.jp/#ebook">book</a> and
  <a href="https://railstutorial.jp/#screencast">screencast</a>
  to teach web development with
  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
  This is the sample application for the tutorial.
</p>

実行

$ rails test
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

ローカルサーバーを起動して確認。

static_pages/about

Refactor

リファクタリングっていつから一般用語になったんだろう?

コンピュータプログラミングにおいて、プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することである。また、いくつかのリファクタリング手法の総称としても使われる。ただし、十分に確立された技術とはいえず、また「リファクタリング」という言葉に厳密な定義があるわけではない。
by wikipedia

ってことらしい。

去年の春になるのか?な会社はほんと必要だったな。

コードの腐敗臭というか化石化というか・・・

少しだけ動的なページ

StaticPagesコントローラのタイトルをテストする

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

まだ各ページにタイトルを入れてないのでテストスイートはREDになる。

タイトルを追加する(GREEN)

完全なHTML構造を備えたHomeページのビュー
app/views/static_pages/home.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Home | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

完全なHTML構造を備えたHelpページのビュー

<!DOCTYPE html>
<html>
  <head>
    <title>Help | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Help</h1>
    <p>  Get help on the Ruby on Rails Tutorial at the
      <a href="https://railstutorial.jp/help">Rails Tutorial help
      page</a>.
      To get help on this sample app, see the
      <a href="https://railstutorial.jp/#ebook">
      <em>Ruby on Rails Tutorial</em> book</a>.
    </p>
  </body>
</html>

完全なHTML構造を備えたAboutページのビュー
app/views/static_pages/about.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>About | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>About</h1>
    <p>
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      is a <a href="https://railstutorial.jp/#ebook">book</a> and
      <a href="https://railstutorial.jp/#screencast">screencast</a>
      to teach web development with
      <a href="http://rubyonrails.org/">Ruby on Rails</a>.
      This is the sample application for the tutorial.
    </p>
  </body>
</html>

ここまでやってテストスイート。

$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

基本タイトルを使ったStaticPagesコントローラのテスト

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
    assert_select "title", "Home | #{@base_title}"
  end

  test "should get help" do
    get static_pages_help_url
    assert_response :success
    assert_select "title", "Help | #{@base_title}"
  end

  test "should get about" do
    get static_pages_about_url
    assert_response :success
    assert_select "title", "About | #{@base_title}"
  end
end

レイアウトと埋め込みRuby (Refactor)

同じコードを繰り返すことはRubyの「DRY」(Don’t Repeat Yourself: 繰り返すべからず) という原則に反する。

ほぉ〜そんな言葉があるのですか。

重複を取り除くテクニックの1つとして、ビューで「埋め込みRuby」(Embedded Ruby) が使える。

タイトルにERBを使ったHomeページのビュー

app/views/static_pages/home.html.erb

<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

タイトルにERBを使ったHelpページのビュー

app/views/static_pages/help.html.erb

<% provide(:title, "Help") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Help</h1>
    <p>  Get help on the Ruby on Rails Tutorial at the
      <a href="https://railstutorial.jp/help">Rails Tutorial help
      section</a>.
      To get help on this sample app, see the
      <a href="https://railstutorial.jp/#ebook">
      <em>Ruby on Rails Tutorial</em> book</a>.
    </p>
  </body>
</html>

タイトルにERBを使ったAboutページのビュー

app/views/static_pages/about.html.erb

<% provide(:title, "About") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>About</h1>
    <p>
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      is a <a href="https://railstutorial.jp/#ebook">book</a> and
      <a href="https://railstutorial.jp/#screencast">screencast</a>
      to teach web development with
      <a href="http://rubyonrails.org/">Ruby on Rails</a>.
      This is the sample application for the tutorial.
    </p>
  </body>
</html>

サンプルアプリケーションのレイアウト

もっとDRYにする。 サンプルアプリケーションのレイアウト

<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                               'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application',
                               'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

HTML構造を削除したHomeページ

app/views/static_pages/home.html.erb

<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  sample application.
</p>

HTML構造を削除したHelpページ

app/views/static_pages/help.html.erb

<% provide(:title, "Help") %>
<h1>Help</h1>
<p>  Get help on the Ruby on Rails Tutorial at the
  <a href="https://railstutorial.jp/help">Rails Tutorial help section</a>.
  To get help on this sample app, see the
  <a href="https://railstutorial.jp/#ebook"><em>Ruby on Rails Tutorial</em>
  book</a>.
</p>

HTML構造を削除したAboutページ

app/views/static_pages/about.html.erb

<% provide(:title, "About") %>
<h1>About</h1>
<p>
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  is a <a href="https://railstutorial.jp/#ebook">book</a> and
  <a href="https://railstutorial.jp/#screencast">screencast</a>
  to teach web development with
  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
  This is the sample application for the tutorial.
</p>

ルーティングの設定

HomeページをルートURLに設定する

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

コミット・masterブランチマージ・デプロイ

$ git add -A
$ git commit -m "Finish static pages"
$ git checkout master
$ git merge static-pages
$ git push
$ rails test
$ git push heroku

デプロイの前にテストをするようにする。

minitest reporters

red や green を表示できるようにする
test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests
  # in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

Guardによるテストの自動化

Guardは、ファイルシステムの変更を監視し、例えばstatic_pages_test.rbファイルなどを変更すると自動的にテストを実行してくれるツールです。具体的には、「home.html.erbファイルが変更されたらstatic_pages_controller_test.rbを自動的に実行する」といったことをGuardで設定することができます。

初期化をする。

$ bundle exec guard init

カスタマイズしたGuardfile

# Guardのマッチング規則を定義
guard :minitest, spring: "bin/rails test", all_on_start: false do
  watch(%r{^test/(.*)/?(.*)_test\.rb$})
  watch('test/test_helper.rb') { 'test' }
  watch('config/routes.rb')    { integration_tests }
  watch(%r{^app/models/(.*?)\.rb$}) do |matches|
    "test/models/#{matches[1]}_test.rb"
  end
  watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
    resource_tests(matches[1])
  end
  watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
    ["test/controllers/#{matches[1]}_controller_test.rb"] +
    integration_tests(matches[1])
  end
  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
    integration_tests(matches[1])
  end
  watch('app/views/layouts/application.html.erb') do
    'test/integration/site_layout_test.rb'
  end
  watch('app/helpers/sessions_helper.rb') do
    integration_tests << 'test/helpers/sessions_helper_test.rb'
  end
  watch('app/controllers/sessions_controller.rb') do
    ['test/controllers/sessions_controller_test.rb',
     'test/integration/users_login_test.rb']
  end
  watch('app/controllers/account_activations_controller.rb') do
    'test/integration/users_signup_test.rb'
  end
  watch(%r{app/views/users/*}) do
    resource_tests('users') +
    ['test/integration/microposts_interface_test.rb']
  end
end

# 与えられたリソースに対応する統合テストを返す
def integration_tests(resource = :all)
  if resource == :all
    Dir["test/integration/*"]  else
    Dir["test/integration/#{resource}_*.rb"]
  end
end

# 与えられたリソースに対応するコントローラのテストを返す
def controller_test(resource)
  "test/controllers/#{resource}_controller_test.rb"
end

# 与えられたリソースに対応するすべてのテストを返す
def resource_tests(resource)
  integration_tests(resource) << controller_test(resource)
end

隠しファイルを表示

  • ファイルナビゲーターのギアアイコンをクリック
  • Show hidden filesをクリック
  • ルートディレクトリにある.gitignoreファイルを表示

.gitignoreにSpringを追加する

# Ignore Spring files.
/spring/*.pid

を追加する。

Guardの設定が完了したら新しいターミナルで次のコマンドを実行。

$ bundle exec guard

最後にコミットしておく。

$ git add -A
$ git commit -m "Complete advanced setup"