今回はRailsで自動テスト(モデルテストとシステムテスト)を書く方法を学びました。
Railsのテストはminitest(ほとんどがtest-unitと同じ)がベースになっています。
なぜテストを書くのか?
- 品質管理:テストは、アプリケーションの品質を保証する重要な手段。テストを書くことで、アプリケーションの動作を継続的に監視し、問題があれば早期に発見できる。
- バグ修正:テストは、バグ修正に役立つ。バグが発生した場合、その原因を特定するのに時間がかかる。しかし、テストを書いていれば、1つのテストが失敗した場合にその原因を特定することができる。(プログラムが壊れると顧客や利用者に迷惑がかかる)
- 変更管理:アプリケーションが進化するにつれて、変更が必要になる場合がある。テストを書いていれば、変更がアプリケーション全体にどのような影響を与えるかを簡単に確認できる。
- ドキュメンテーション:テストは、アプリケーションのドキュメンテーションとしても役立つ。テストを書くことで、アプリケーションの動作と想定される振る舞いを文書化することができる。
- 手作業で動作確認するよりも、自動化した方が最終的な費用対効果が高いから。
なぜテストを書くの?(または書かないの?) 〜テストコードの7つの役割〜
学んだこと✏️
テストに何を書けばいいかの基本的な考え
modelとhelper
- 自分がmodelに作成したメソッドに対し、最低一つ(正常系)を書く。
controller
- 書かなくても良い。
modelのvalidationなど、railsの機能、gemの機能をテストしてしまわないようにする。(gemのテストはgemの中でそれぞれやってるので必要ない)
Railsアプリにテストを追加
Userクラスのモデルテストを作成
test/fixtures/users.yml
john: email: john@example.com encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %> name: John mike: email: mike@example.com encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %> name: Mike alice: email: alice@example.com encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %> name: Alice
test/models/users_test.rb
require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @john = users(:john) @mike = users(:mike) @alice = users(:alice) end test 'following? should return true if user is being followed' do @john.follow(@mike) assert @john.following?(@mike) end test 'following? should return false if user is not being followed' do assert_not @john.following?(@alice) end test 'followed_by? should return true if user is being followed by another user' do @mike.follow(@john) assert @john.followed_by?(@mike) end test 'followed_by? should return false if user is not being followed by another user' do assert_not @john.followed_by?(@alice) end test 'unfollow should destroy a relationship' do @john.follow(@mike) assert @john.following?(@mike) @john.unfollow(@mike) assert_not @john.following?(@mike) end test 'name_or_email should return name if present' do @john.name = 'John' @john.email = 'john@example.com' assert_equal 'John', @john.name_or_email end test 'name_or_email should return email if name is not present' do @john.name = nil @john.email = 'john@example.com' assert_equal 'john@example.com', @john.name_or_email end end
- 内部実装は変更される可能性もあるため、なるべく外部に見える振る舞いを中心にテストする。
Reportクラスのモデルテストの作成
test/fixtures/reports.yml
john_report: title: John's Report content: This is John's report user: john mike_report: title: Mike's Report content: This is Mike's report user: mike
- 連番(
report1:
とか)は数が増えると脳内マッピングが追いつかなくなるため、「ジョンの日報」「マイクの日報」等、連番以外で特徴付けられる名前を付けた方が良い。(他のfixtureも同様) created_at
はRailsが自動的に設定するもの。fixtureで勝手に決められていると、チーム開発時に他の開発者が戸惑う為書かない。
test/models/report_test.rb
require 'test_helper' class ReportTest < ActiveSupport::TestCase def setup @john = users(:john) @john_report = reports(:john_report) end test 'editable? returns true if the target user is the owner of the report' do assert @john_report.editable?(@john) assert_not @john_report.editable?(users(:mike)) end test 'created_on should return the date of the report creation' do assert_equal Time.current.to_date, @john_report.created_on end end
期待値はなるべくベタ書きした方が良い。
actual側はなるべく加工しない形でテストできるようにする。
システムテストの作成
test/system/reports_test.rb
require 'application_system_test_case' class ReportsTest < ApplicationSystemTestCase setup do @report = reports(:john_report) visit root_url fill_in 'Eメール', with: 'john@example.com' fill_in 'パスワード', with: 'password' click_on 'ログイン' end test 'visiting the index' do visit reports_url assert_selector 'h1', text: '日報' end test 'creating a Report' do visit reports_url click_on '新規作成' fill_in 'タイトル', with: '本日の日報' fill_in '内容', with: 'Railsでテストを書く' click_on '登録する' assert_text '日報が作成されました。' assert_text '本日の日報' assert_text 'Railsでテストを書く' end test 'updating a Report' do visit reports_url click_link '詳細', match: :first assert_text "John's Report" assert_text "This is John's report" click_on '編集', match: :prefer_exact fill_in 'タイトル', with: 'ジョンのレポート' fill_in '内容', with: 'これはジョンのレポートです' click_on '更新する' assert_text '日報が更新されました。' assert_text 'ジョンのレポート' assert_text 'これはジョンのレポートです' end test 'destroying a Report' do visit reports_url assert_text "John's Report" page.accept_confirm do click_on '削除', match: :first end assert_text '日報が削除されました。' assert_no_text "John's Report" end end
- 変更前は「あいうえお」だったが、変更後は「かきくけこ」に変わった、というようなテストの方が良い。 (変更前も「かきくけこ」だったら、ちゃんと変更できていない可能性があるため)