Rails で実装した機能がうまく動作するか、もしくは無効な値が入力された場合にちゃんとエラーを吐き出してくれるかどうか(バリデーションが効いているか)。
これらをシステム的に確認してくれるのがテスト。
ただ、ぶっちゃけた話、アプリの実装に加えてテストのコードまで書くのは正直面倒くさいので、ブラウザ上でちゃんと機能するか確認すれば良くね?
って思っていました、最初はw
ただ、アプリの規模が大きくなってきた時に、変更やアップデートを行うたびにブラウザ上でいちいち動作確認をするのはもっと面倒だよな、
と思い始めて、それなら最初からテストコード書いておいた方が後々楽になるだろう、だから今のうちにテストコードについて学んでおこう、
それにもし今後本格的なWebサービスを作るなら、致命的な不具合を出さないためにもテストコードは必須になるよな、、

ということで、今回はRailsでテスト(単体テスト、統合テスト)を行う手順について学んだことを、今後のためにもメモしておこうと思います。
開発環境
- Ruby 3.1.2
- Ruby on Rails 7.0.3
- M1 Macbook Air 2020
- mac OS Monterey (ver. 12.4)
- ターミナル bash (Rosetta 2 使用)
ソースコード
単体テストと統合テスト
Rails のテストは主に2種類に分類され、一つは「単体テスト」、もう一つは「統合テスト」と呼ばれています。
「単体テスト」は一つの機能に関して正常に動くかどうかを見るテストのことで、
モデルのバリデーションや、コントローラーの動作を一つ一つ確認していく時には、この単体テストを行います。
「統合テスト」は、複数の機能が連動して行われる処理が正常に動くかどうかを見るテストのことで、
例えばユーザーがログインしてからログアウトするまでの一連の流れがうまく機能するかどうかをみたい時に、この統合テストを行います。


ちなみに、テストコードを書く際に用いられるテスティングフレームワークは色々ありますが、ここではRailsにデフォルトで入っているminitestを用いて話を進めていきます。
(最も有名かつポピュラーなテスティングフレームワークはRSpecだそうです。僕もいずれはRSpecに移行しようと思っています)
単体テストを行う流れ
Rails は以下のコマンドでモデルを作成すると、自動的にモデルのテスト用ファイルも作成してくれます。
$ rails g model User name:string email:string password_digest:string
...
create app/models/user.rb
create test/models/user_test.rb
create test/fixtures/users.yml
...
コントローラーを作成した場合は、コントローラー用のテストファイルが自動生成されます。
($ rails scaffold
した場合は、モデル用、コントローラー用の両方のテストファイルが生成されます)
ここでは、Userモデルを作成したというていで話を進めていきましょう。
Userモデルではnameやemail、passwordに対してさまざまなバリデーションを効かせると思いますが、
これらのバリデーションがちゃんと効いているかどうかを一つ一つ確認していきます。
Userモデルのテストを行うためには、test/models
ディレクトリ内のuser_test.rb
にテストコードを記述していきます。
例えば、Userモデルのnameの値を必須にしたい(空にしたらエラーが出るようにしたい)場合は、以下のようなテストコードを記述してテストします。
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "hiroking",
email: "hiroking@example.com",
password: "hiroking",
password_confirmation: "hiroking"
)
end
test "name should be present" do
@user.name = " "
assert_not @user.valid?
end
end
“name should be present” と命名されたテストでは、
ユーザー名がnil
のときに( @user.name = ” “)、assert_not で @user.valid? が false になってくれればテスト成功となります。
assert_not
はアサーションの一つで、オブジェクトまたは式を評価して、期待された結果が得られるかどうかをチェックするコードのことです。
利用可能なアサーション一覧はRailsの公式ドキュメントを参照。
続いて、モデルの単体テストを実行する場合は、以下のコマンド実行します。
$ rails test:models
先ほどのテストコード “name should be present” でテストがパスしなかった場合は以下のようにFailure:
と表示されます。
# Running:
......F
Failure:
UserTest#test_name_should_be_present [/Users/hirokirokki0820/Desktop/Ruby_on_Rails/user-authentication/test/models/user_test.rb:18]:
Expected true to be nil or false
rails test test/models/user_test.rb:16
この「Expected true to be nil or false」という文面から、
name
カラムがnil
の時の期待値はfalse
になるはずなのにtrue
になってるよ、ということが読み取れます。
(nameが空っぽなのに、ユーザー登録できちゃってるよ?という意味)
この結果から、Userモデルのname
カラムにバリデーションが効いていないことが発覚しました。
バリデーションを効かせて、もう一度実行するとテストはパスするはずです。
ターミナルに以下のような表示が出たら、テストがパスした証拠です。
Running 15 tests in a single process (parallelization threshold is 50)
Run options: --seed 15386
# Running:
...............
Finished in 3.174605s, 4.7250 runs/s, 8.5050 assertions/s.
15 runs, 27 assertions, 0 failures, 0 errors, 0 skips
今回はnameカラムを例に挙げましたが、他にもemail, passwordカラムなど、各カラムごとに必要なテストコードを追記し、一つ一つ機能するか確認していきます。
一応、今回Userモデル(name, email, password)に適用したバリデーション、およびバリデーションチェックで必要そうなテストコードのサンプルを置いておきます。
class User < ApplicationRecord
# email オブジェクトが保存される時点で小文字に変換する
before_save { self.email = email.downcase }
# name のバリデーション
validates :name, presence: true, length: { maximum: 25 }
# email のバリデーション
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
uniqueness: { case_sensitive: false },
length: { maximum: 105 },
format: { with: VALID_EMAIL_REGEX }
# password のバリデーション
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
end
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "hiroking",
email: "hiroking@example.com",
password: "hiroking",
password_confirmation: "hiroking"
)
end
test "should be valid" do
assert @user.valid?
end
test "name should be present" do
@user.name = " "
assert_not @user.valid?
end
test "email should be present" do
@user.email = " "
assert_not @user.valid?
end
test "name should not be too long" do
@user.name = "a" * 100
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 255 + "example.com"
assert_not @user.valid?
end
test "email validation should accept valid addresses" do
invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
foo@bar_baz.com foo@bar+baz.com]
invalid_addresses.each do |invalid_address|
@user.email = invalid_address
assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
test "email addresses should be unique" do
duplicate_user = @user.dup
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
test "email addresses should be saved as lower-case" do
mixed_case_email = "Foo@ExAMPle.CoM"
@user.email = mixed_case_email
@user.save
assert_equal mixed_case_email.downcase, @user.email
end
test "password should be present (nonblank)" do
@user.password = @user.password_confirmation = " " * 6
assert_not @user.valid?
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end
test "password should be confirmed" do
@user.password = "a" * 10
@user.password_confirmation = "b"* 10
assert_not @user.valid?
end
end
統合テストを行う流れ
今回はユーザーの新規登録の一連の流れをテストするというていで話を進めます。
以下のコマンドを実行し、test/integration
ディレクトリにテスト用のファイルを作成します。
$ rails g integration_test (テスト名)
(テスト用のファイルは(テスト名)_test.rb
という名前で出力される)
今回はユーザーの新規登録のテストということで、テスト名はusers_signup
としました。
test/integration
フォルダ内にはusers_signup_test.rb
が作成されます。
require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest
# この中にテストコードを記述する
end
テストコードは上記の UsersSignupTest
クラス内に書いていきます。
では、まずユーザー登録時に無効な値を入れるとユーザーが作成されない(登録に失敗する)ことを確認するテストを記述していきましょう。
require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest
# 新規ユーザー登録用の統合テスト
# ユーザー登録時に(ユーザー情報が無効であるために)ユーザーが作成されないことを確認する
test "invalid signup information" do
get new_user_path
# 期待値:ユーザー数の合計が変化しない(ユーザー登録に失敗)
assert_no_difference 'User.count' do
# users_pathに対してポストリクエストを送信する
post users_path, params:
{ user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
# エラー発生時に new_user_path をレンダリングしているかどうか
assert_template "users/new"
end
end
テストコードを書いたら、ターミナルで以下のコマンドを実行してテストを行います。
$ rails test
上記のテストコードでは、
- ユーザーが新規登録フォームにアクセスする
- 登録情報に無効な値を入力する(空にする、無効なメアド入力、パスワードの不一致など)
- 登録に失敗したら、新規登録フォームをレンダリングする
という一連の流れが実行できているかどうかをチェックしています。
ユーザーの登録に失敗しているかどうかは、テストコード内の
assert_no_difference 'User.count' do
この部分で、データーベース内のユーザー数の値(カウント数)が増えていないかどうかで判断しています。
ユーザー登録に失敗していたらユーザー数の合計は増えないはずですからね。
続いては、ユーザー登録時に有効な値を入れるとユーザーが作成される(登録に成功する)ことを確認するテストを記述していきましょう。
先ほどのテストの下につづけて、以下のように記述します。
require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest
# 新規ユーザー登録用の統合テスト
# ユーザー登録時に(ユーザー情報が無効であるために)ユーザーが作成されないことを確認する
test "invalid signup information" do
(省略)
end
# ユーザー登録時にユーザーが作成されることを確認する
test "valid signup information" do
get new_user_path
# 期待値:ユーザー数の合計が+1される(ユーザー登録に成功したのと同意)
assert_difference 'User.count', 1 do
post users_path, params: { user:
{ name: 'test user',
email: 'test@example.com',
password: 'password',
password_confirmation: 'password' } }
end
assert_response :redirect
#リダイレクト実行後に続いて別のリクエストを行う予定がある場合は、follow_redirect!を呼び出す
follow_redirect!
assert_response :success
assert_template "users/show"
# showページの指定タグ内にユーザー名が表示されているかを確認
assert_select "p.header", "test user"
assert_select "div.ui.card>.content>.header", "test user"
# showページにフラッシュメッセージが表示されているかを確認
assert_select "div.ui.message.success"
end
end
今回はユーザー登録の一連の流れをテストするコードのサンプルを挙げましたが、
ログインやログアウト、記事の投稿や削除、もしくはその全ての一連の流れをテストする場合は、
別途ファイルを作成して上記のような感じでテストコードを書いていきます。
以上、テストコード作成の流れでした。
コメント