Ruby on Rails:テスト

モデルのテストを書く

テストを書くのは自分自身で def したメソッドがあるモデル。理由としては、それ以外の部分については、フレームワークとして信用できるのでわざわざテストする必要はないため。

例えば、次のようなモデルがあるとする。

class User < ApplicationRecord
  ...
  def following?(user)
    active_relationships.where(following_id: user.id).exists?
  end

  def follow(user)
    active_relationships.find_or_create_by!(following_id: user.id)
  end

  def unfollow(user)
    relationship = active_relationships.find_by(following_id: user.id)
    relationship&.destroy!
  end
  ...
end

上記のフォロー、アンフォローメソッドのテストする。まずは、モデルテストのスケルトンを作る。

% bin/rails generate test_unit:model モデル名 
create  test/models/user_test.rb
create  test/fixtures/users.yml

上記コマンドの引数に、属性名と型(例:title:string)を与えると、fixtures にテンプレートが用意される。fixtureは、『事前に用意したテストデータを読み込み、常にデータベースの内容を一定に保つための仕組み』のこと。

とりあえずfixtureを使わずにテストを書くので、 test/fixtures/users.yml は全てコメントアウトしておく。

# test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
  test '#follow' do
    he = User.create!(email: 'he@example.com', password: 'password')
    her = User.create!(email: 'her@example.com', password: 'password')
    assert_not he.following?(her)
    he.follow(her)
    assert he.following?(her)
  end

  test '#unfollow' do
    he = User.create!(email: 'he@example.com', password: 'password')
    her = User.create!(email: 'her@example.com', password: 'password')

    he.follow(her)
    assert he.following?(her)
    he.unfollow(her)
    assert_not he.following?(her)
  end
end

上記はテストメソッド内でデータを生成しているが、fixtureを使うと事前にテストデータを用意できる。

# test/fixtures/users.yml
he:
  email: 'he@example.com'

her:
  email: 'her@example.com'

テストメソッド内でfixtureを使うために、少しテストコードを修正する。

# test/models/user_test.rb
class UserTest < ActiveSupport::TestCase
  fixtures :users
  test '#follow' do
    he = users(:he)
    her = users(:her)
    assert_not he.following?(her)
    he.follow(her)
    assert he.following?(her)
  end

  test '#unfollow' do
    he = users(:he)
    her = users(:her)
    he.follow(her)
    assert he.following?(her)
    he.unfollow(her)
    assert_not he.following?(her)
  end
end

テストメソッドに重複が現れたので、setupメソッド(各テストの実行前に呼ばれるメソッド)でDRYにする。

# test/models/user_test.rb
class UserTest < ActiveSupport::TestCase
  fixtures :users

  setup do
    @user = users(:hoge)
    @he = users(:he)
    @her = users(:her)
  end

  test '#follow' do
    assert_not @he.following?(@her)
    @he.follow(@her)
    assert @he.following?(@her)
  end

  test '#unfollow' do
    @he.follow(@her)
    assert @he.following?(@her)
    @he.unfollow(@her)
    assert_not @he.following?(@her)
  end
end

関連付けを行っている場合のフィクスチャ

関連付けを行っている場合には、2つの異なるフィクスチャの間に参照ノードを定義する。例えば、次のような関連があるモデルに対して、report モデルのフィクスチャを定義することを考える。

image.png

まずは、 user モデルのフィクスチャを設定。

# test/fixtures/users.yml
hoge:
  email: 'hoge@example.com'

次に関連付けされている report モデルにフィクスチャを設定する。

# test/fixtures/reports.yml
one:
  user: hoge
  title: test
  body: hogehgoe

関連付けされている属性は、 user モデルで定義したフィクスチャ hoge を利用する。そうしてあげることで、 onehoge が関連付けされる。

Railsのテストランナー

rails でテストを実行するには、 rails test を叩けば良い。その際に、オプションを指定することでテスト実行の範囲を指定できる。

コマンド 内容
rails test 全てのテストを一括実行
rails test テストファイルのパス 指定したテストファイル内のテストケースを実行
rails test テストファイルのパス -n テストケース名 指定したテストファイル内の指定したテストケース名を実行。接頭辞として test_ をつけて空白は _ に置換して渡す。
rails test テストファイルのパス:行番号 指定したテストファイルの指定した行のテストケースを実行
rails test テストファイルのあるディレクトリパス 指定したディレクトリパス内のテストファイルないのテストケースを実行
rails test -h テストランナーのヘルプを確認する

システムテスト

システムテストを実行するときは下記コマンドを叩く。

% rails test:system

システムテストのテンプレートは、下記コマンドで生成できる。

% rails generate system_test reports
# => create test/system/reports_test.rb

ちょっと書いてみた

assert_selector を完全一致で使う

例えば、次のように本一覧ページにアクセスできることをテストしたいとする。

image.png

<h1> タグに 『本』が含まれていれば良いと考えて、次のようにテストを書いた。

# test/system/books_test.rb
  test 'visiting the index' do
    visit books_url
    assert_selector 'h1', text: ''
  end

ただ、上記だと、本の詳細ページも h1 タグがあり『本』が含まれているので、詳細ページへ間違ってリンクされていたとしてもテストが通ってしまう。

image.png

実際に下記のようなテストを実施してみると、テストが通ってしまう💦

# test/system/books_test.rb

test 'visiting the index' do
  # 本一覧ページのテスト
  visit books_url
  assert_selector 'h1', text: ''

  # 本詳細ページのテスト
  click_on '新規作成'
  fill_in 'タイトル', with: 'チェリー本'
  fill_in 'メモ', with: 'わかりやすい'
  fill_in '著者', with: 'Junichi Ito'
  click_on '登録する'
  assert_selector 'h1', text: ''
end

これは、 assert_selector 'h1', text: '本' のアサートでは部分一致となっているため。

assert_selector の説明には、下記のような記述があり、Finders#all で受け付けられるオプションを使用できる。

It also accepts all options that Finders#all accepts,

assert_selector

Finder#all を覗いてみると下記の記述がある。

text (String, Regexp) - このテキストを含むか、この正規表現にマッチする要素のみを検索します。

exact_text (String, Regexp, String) - String の場合は、 要素に含まれるテキストが正確に一致する必要があり 、 Boolean の場合は、 テキストオプションが正確に一致する必要があるかどうかを制御します。

Module: Capybara::Node::Finders

完全一致にしてあげるために次のようにしてみる。

  test 'visiting the Report index' do
    visit reports_url

    # 正規表現で完全一致
    assert_selector 'h1', text: /^$/
    # exact_text で完全一致
    assert_selector 'h1', exact_text: '日報'
  end

これで、h1 が意図した値に完全一致するかを指定して検証できるようになった🤗

参考