【Rails#12】単体テストの実装とバグ潰し【Model】

【Rails#12】単体テストの実装とバグ潰し【Model】

コジマです。

【Rails#11】モデルのテストデータ作ろうぜ【Fixture】
の続きです。

思ったより長くなってしまったんですよね。

対象読者は前回と同じ

<

h1>対象読者

Progate終わったけどテストの方法も覚えたい、って人
Railsでテスト作る時どう考えりゃいいんだ?って人
Fixtureやminitest復習したい人

この記事でやること

今回は

  • モデルの単体テストの実装
  • バグ潰し(モデルのバリデーション定義)

をしていきたいと思います。

予めお断りしておきたいのですが、
独学なのでこれで動くテストはできても正しい実装になっているかはわからないです。
申し訳ないです。

方針

方針としては
ユーザーデータに特定の値をセットし、user.valid実行時の戻り値を見る
trueであってほしいもの(正常系)はassert
falseであってほしいもの(異常系)はassert_notで判定していこうかと思います。

テストを作る

user_test.rbは上記の方針でこうなりました。

require 'test_helper'

class UserTest < ActiveSupport::TestCase
  def setup
    @user = User.new(
      login_id: "hoge",
      display_name: "hoge",
      password:"hogehoge",
      password_confirmation: "hogehoge",
      email: "hoge@email.com",
      authority_flag: "0"
    )
  end

    #login_id(異常系)
    #重複する2ユーザー
    test "check login_id is duplicate invalid" do
      @user.login_id = "test"
      assert_not @user.valid?,"login_id can't duplicated"
    end

    #記号を含む
    test "check login_id include symbol invalid" do
      @user.login_id = "test@"
      assert_not @user.valid?,"login_id can't include symbols"
    end

    #全角文字を含む
    test "check login_id include wide charactor invalid" do
      @user.login_id = "テスト"
      assert_not @user.valid?,"login_id can't include wide charactors"
    end

    #nil
    test "check login_id is nil invalid" do
      @user.login_id = ""
      assert_not @user.valid?,"login_id is not nil"
    end

    #3文字
    test "check login_id is too short invalid" do
      @user.login_id = "foo"
      assert_not @user.valid?,"login_id is more than 4 charactors"
    end

    #21文字
    test "check login_id is too long invalid" do
      @user.login_id = "abcdeabcdeabcdeabcdea"
      assert_not @user.valid?,"login_id is less than 20 charactors"
    end

    #display_name(異常系)
    #nil
    test "check display_name is nil invalid" do
      @user.display_name = ""
      assert_not @user.valid?,"display_name is not nil"
    end

    #21文字
    test "check display_name is too long invalid" do
      @user.display_name = "21文字の名前の長いユーザー名にしています"
      assert_not @user.valid?,"display_name is less than 20 charactors"
    end

    #password(正常系)
    #記号を含む
    test "check password include symbol invalid" do
      @user.password = "test/"
      @user.password_confirmation = "test/"
      assert_not @user.valid?,"password can't include symbols"
    end
    #全角文字を含む
    test "check password include wide charactor invalid" do
      @user.password = "パスワード"
      @user.password_confirmation = "パスワード"
      assert_not @user.valid?,"password can't include wide charactors"
    end
    #nil
    test "check password is nil invalid" do
      @user.password = ""
      @user.password_confirmation = ""
      assert_not @user.valid?,"password is not nil"
    end
    #3文字
    test "check password is too short invalid" do
      @user.password = "foo"
      @user.password_confirmation = "foo"
      assert_not @user.valid?,"password is more than 4 charactors"
    end
    #21文字
    test "check password is too long invalid" do
      @user.password = "testtesttesttesttesta"
      @user.password_confirmation = "testtesttesttesttesta"
      assert_not @user.valid?,"password is less than 20 charactors"
    end

  #email(異常系)
    #重複する2ユーザー
    test "check email duplicate invalid" do
      @user.email = "test@email.com"
      assert_not @user.valid?,"email must be unique"
    end
    #@がない
    test "check email not include @ invalid" do
      @user.email = "test.com"
      assert_not @user.valid?,"email must be mail format"
    end
    #全角文字含む
    test "check email include wide characters invalid" do
      @user.email = "テスト@test.com"
      assert_not @user.valid?,"email can't include wide characters"
    end
    #nil
    test "check email is nil invalid" do
      @user.email = ""
      assert_not @user.valid?,"email is not nil"
    end

  #authority_flag(異常系)
    #nil
    test "check authority_flag is nil invalid" do
      @user.authority_flag = ""
      assert_not @user.valid?,"authority_flag is not nil"
    end
    #2
    test "check authority_flag is other number of 0 or 1 invalid" do
      @user.authority_flag = "2"
      assert_not @user.valid?,"authority_flag must be 0 or 1"
    end
    #全然関係ない文字
    test "check authority_flag has nothing to do with number invalid" do
      @user.authority_flag = "A"
      assert_not @user.valid?,"authority_flag must be 0 or 1"
    end

  #正常系
  #login_id 4文字、display_name 1文字 password 4文字
  #明示的に再宣言
    test "check minimum length valid" do
      @user.login_id = "hoge"
      @user.display_name = "hoge"
      @user.password = "hoge"
      @user.password_confirmation = "hoge"

      assert @user.save,"check minimum length"
    end

    #login_id 4文字、display_name 1文字
    #password 4文字 authority_flag 1
    test "check admin user valid" do
      @user.login_id = "hogehogeho"
      @user.display_name = "管理者"
      @user.password = "hogehogeho"
      @user.password_confirmation = "hogehogeho"
      @user.authority_flag = "1"

      assert @user.save,"check admin"
    end

    #login_id 20文字、display_name 20文字 password 20文字
    test "check max length valid" do
      @user.login_id = "abcdeabcdeabcdeabcde"
      @user.display_name = "abcdeabcdeabcdeabcde"
      @user.password = "abcdeabcdeabcdeabcde"
      @user.password_confirmation = "abcdeabcdeabcdeabcde"

      assert @user.save,"check minimum length"
    end
end

すっごい長いですが、単体試験はこんなもんかと。

minitest-reportersの設定

test_helperを編集して以下のようにします。

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

使い方はRailsチュートリアルにも書いていますが。
公式のgithubにも書いています。

テストを実行

今回テストするのはユーザーモデルだけに絞るのでこんな感じでコマンド実行

$ rails test test/models/user_test.rb 


minitest-reportersを導入するとこうしてカラーで出てきます。
とても見やすくなります。

全文はこんな感じ

$ rails test test/models/user_test.rb 
Running via Spring preloader in process 13071
Started with run options --seed 3259

 FAIL["test_check_login_id_is_nil_invalid", Minitest::Result, 0.058835628999077016]
 test_check_login_id_is_nil_invalid#Minitest::Result (0.06s)
        login_id is not nil
        test/models/user_test.rb:37:in `block in <class:UserTest>'

 FAIL["test_check_login_id_is_too_short_invalid", Minitest::Result, 0.07730235399867524]
 test_check_login_id_is_too_short_invalid#Minitest::Result (0.08s)
        login_id is more than 4 charactors
        test/models/user_test.rb:43:in `block in <class:UserTest>'

 FAIL["test_check_display_name_is_too_long_invalid", Minitest::Result, 0.10149881400138838]
 test_check_display_name_is_too_long_invalid#Minitest::Result (0.10s)
        display_name is less than 20 charactors
        test/models/user_test.rb:62:in `block in <class:UserTest>'

 FAIL["test_check_password_include_wide_charactor_invalid", Minitest::Result, 0.1173242949989799]
 test_check_password_include_wide_charactor_invalid#Minitest::Result (0.12s)
        password can't include wide charactors
        test/models/user_test.rb:76:in `block in <class:UserTest>'

 FAIL["test_check_authority_flag_is_nil_invalid", Minitest::Result, 0.12005681099981302]
 test_check_authority_flag_is_nil_invalid#Minitest::Result (0.12s)
        authority_flag is not nil
        test/models/user_test.rb:123:in `block in <class:UserTest>'

 FAIL["test_check_password_include_symbol_invalid", Minitest::Result, 0.1337837649989524]
 test_check_password_include_symbol_invalid#Minitest::Result (0.13s)
        password can't include symbols
        test/models/user_test.rb:70:in `block in <class:UserTest>'

 FAIL["test_check_display_name_is_nil_invalid", Minitest::Result, 0.14728031599952374]
 test_check_display_name_is_nil_invalid#Minitest::Result (0.15s)
        display_name is not nil
        test/models/user_test.rb:56:in `block in <class:UserTest>'

 FAIL["test_check_email_not_include_@_invalid", Minitest::Result, 0.17270141500193859]
 test_check_email_not_include_@_invalid#Minitest::Result (0.17s)
        email must be mail format
        test/models/user_test.rb:106:in `block in <class:UserTest>'

 FAIL["test_check_password_is_too_long_invalid", Minitest::Result, 0.18367412500083447]
 test_check_password_is_too_long_invalid#Minitest::Result (0.18s)
        password is less than 20 charactors
        test/models/user_test.rb:94:in `block in <class:UserTest>'

 FAIL["test_check_login_id_include_wide_charactor_invalid", Minitest::Result, 0.19625735500085284]
 test_check_login_id_include_wide_charactor_invalid#Minitest::Result (0.20s)
        login_id can't include wide charactors
        test/models/user_test.rb:31:in `block in <class:UserTest>'

 FAIL["test_check_email_is_nil_invalid", Minitest::Result, 0.2088199159989017]
 test_check_email_is_nil_invalid#Minitest::Result (0.21s)
        email is not nil
        test/models/user_test.rb:116:in `block in <class:UserTest>'

 FAIL["test_check_email_include_wide_characters_invalid", Minitest::Result, 0.22340923600131646]
 test_check_email_include_wide_characters_invalid#Minitest::Result (0.22s)
        email can't include wide characters
        test/models/user_test.rb:111:in `block in <class:UserTest>'

 FAIL["test_check_login_id_is_too_long_invalid", Minitest::Result, 0.23724529500032077]
 test_check_login_id_is_too_long_invalid#Minitest::Result (0.24s)
        login_id is less than 20 charactors
        test/models/user_test.rb:49:in `block in <class:UserTest>'

 FAIL["test_check_password_is_too_short_invalid", Minitest::Result, 0.25036597100188374]
 test_check_password_is_too_short_invalid#Minitest::Result (0.25s)
        password is more than 4 charactors
        test/models/user_test.rb:88:in `block in <class:UserTest>'

 FAIL["test_check_authority_flag_has_nothing_to_do_with_number_invalid", Minitest::Result, 0.26820372699876316]
 test_check_authority_flag_has_nothing_to_do_with_number_invalid#Minitest::Result (0.27s)
        authority_flag must be 0 or 1
        test/models/user_test.rb:133:in `block in <class:UserTest>'

 FAIL["test_check_authority_flag_is_other_number_of_0_or_1_invalid", Minitest::Result, 0.27078463100042427]
 test_check_authority_flag_is_other_number_of_0_or_1_invalid#Minitest::Result (0.27s)
        authority_flag must be 0 or 1
        test/models/user_test.rb:128:in `block in <class:UserTest>'

 FAIL["test_check_login_id_is_duplicate_invalid", Minitest::Result, 0.2827955819993804]
 test_check_login_id_is_duplicate_invalid#Minitest::Result (0.28s)
        login_id can't duplicated
        test/models/user_test.rb:19:in `block in <class:UserTest>'

 FAIL["test_check_email_duplicate_invalid", Minitest::Result, 0.29472553500090726]
 test_check_email_duplicate_invalid#Minitest::Result (0.29s)
        email must be unique
        test/models/user_test.rb:101:in `block in <class:UserTest>'

 FAIL["test_check_login_id_include_symbol_invalid", Minitest::Result, 0.30534323699976085]
 test_check_login_id_include_symbol_invalid#Minitest::Result (0.31s)
        login_id can't include symbols
        test/models/user_test.rb:25:in `block in <class:UserTest>'

  23/23: [================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.30617s
23 tests, 23 assertions, 19 failures, 0 errors, 0 skips

login_id:6項目
password:5項目
display_name:2項目
email:4項目
authority_flag:3項目
正常系:3項目
計23項目
で、正常系3項目、パスワードのnilチェック以外はまだ試験に失敗した状態です。

残り19項目の試験を通るようにモデルを定義していきます。

モデルを定義する

class User < ApplicationRecord
  has_secure_password
end

bcryptを使用するためにhas_secure_passwordだけは書いてました。
他の定義もしていきます。

class User < ApplicationRecord
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  VALID_HALF_CHARACTER_REGEX = /\A[a-zA-Z0-9]+\z/i
  has_secure_password

  validates :login_id,
    presence: true,
    uniqueness: true,
    length: { maximum: 20,minimum: 4},
    format: { with: VALID_HALF_CHARACTER_REGEX }
  validates :display_name,
    presence: true,
    length: { maximum: 20}
  validates :password,
    presence: true,
    length: { maximum: 20,minimum: 4},
    format: { with: VALID_HALF_CHARACTER_REGEX }
  validates :email,
    presence: true,
    uniqueness: true,
    format: { with: VALID_EMAIL_REGEX }
  validates :authority_flag,
    presence: true
  validates_inclusion_of :authority_flag, in: %w( 0 1 )
end

前回の記事で書いた仕様に沿ってモデルを定義しました。

いざ、尋常に

テストを実行する。これで

$ rails test test/models/user_test.rb 

できました!

あぁ、よかった。

本日スタテクさんのもくもく会に行っていたのですが、
authority_flagの設計がよろしくないというレビューを受けたので、
次回はカラムの定義の変更、前回と今回の記事の補足など書いていきたいと思います。

以上、コジマでした。


Rubyカテゴリの最新記事