83
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【RSpec】Railsのモデルスペックでバリデーションのテストは書くべきか否か?

Last updated at Posted at 2022-08-21

はじめに:単純なバリデーションならテストコードは不要(という私見)

Railsでモデルスペックを書くとき、バリデーションのテストを書く人をよく見かけます。
たとえば以下のようなイメージです。

class User < ApplicationRecord
  validates :name, presence: true
end
RSpec.describe User, type: :model do
  # こういうテストは書く?書かない??
  describe 'validation' do
    example 'nameは必須' do
      user = User.new(name: '')
      expect(user).to be_invalid

      user.name = 'Alice'
      expect(user).to be_valid
    end
  end
end

決して、バリデーションのテストを書くことが悪いことだとは言いませんが、個人的には単純なバリデーションであれば別に書かなくてもいいかな、と考えています。

単純なバリデーションにテストコードが不要な理由

その理由は以下のとおりです。

  • presence: trueのような単純なバリデーションはロジックというよりも設定に近いので、わざわざテストする価値が低い
  • 業務レベルのコードだと1モデルあたりのバリデーションの数は非常に多くなるため、単純なバリデーションまでテストを書くと、テストを書く時間とテストコードの量が無駄に増えてしまう。テストコードが増えるとテストの実行時間も遅くなる
  • バリデーションは一度追加すれば、将来「presence: trueをうっかり消してしまった!」とか、「○○というカラムを追加したせいで、nameの必須チェックが効かなくなった!」みたいな問題(いわゆる「デグレ」)が起きにくい

また、テストコード初心者の人の中には「何かテストを書かなきゃ → バリデーションのテストなら簡単だから書けるぞ! → よし書けた。これでOK」と考えている(ように見える)人もいます。つまり、テストを書くこと自体が目的になり、その目的を満たすために一番簡単なバリデーションのテストを書いてお茶を濁す感じです。

しかし、本来であれば「何でもいいから、とりあえず書けばOK」ではなく、しっかりとした目的や戦略をもってテストコードを書くべきです。こうした議論は以下の記事に詳しく書いています。

もちろん、テストコード初心者の人が「はじめの一歩」としてバリデーションのテストを書くのは価値があると思います。つまり、学習目的で「何でもいいからとりあえず書いてみる」というのであればOKです。ですが、業務だとあまり大きなメリットがないので、別に書かなくて良いと僕は考えています。

こんな場合はバリデーションのテストを書くことに価値がある

とはいえ、バリデーションのテストを書かないというのはあくまで原則です。以下のようなケースではちゃんとテストを書いた方がいいでしょう。

バリデーションの設定漏れが致命的な問題につながる場合

「単純な必須チェックだが、万に一つでもこの値が未入力になっていると大金が失われたり、人命が失われたりする可能性がある」というような場合は、ちゃんとテストを書いておくべきです(もちろん、データベース側でもNOT NULL制約を付けましょう)。

つまり、自分が追加したバリデーションがアプリケーション上、どういう意味(どれくらいの重要性)を持っているのかをちゃんと吟味する必要があります。

バリデーションが単純ではない場合

バリデーションが単純ではなく、何かしらのロジックや、様々な判定条件を持っている場合もテストを書いた方がいいです。代表的な例は正規表現ですね。

class Address < ApplicationRecord
  validates :postal_code, format: { with: /\A\d{3}-?\d{4}\z/ }
end
RSpec.describe Address, type: :model do
  describe 'validation' do
    example '適切な形式の郵便番号だけを許可する' do
      # ハイフンあり(OK)
      address = Address.new(postal_code: '123-4567')
      expect(address).to be_valid

      # ハイフンなし(OK)
      address.postal_code = '1234567'
      expect(address).to be_valid

      # 桁数がおかしい(NG)
      address.postal_code = '12-4567'
      expect(address).to be_invalid
      address.postal_code = '123-45678'
      expect(address).to be_invalid

      # 数字でない文字が含まれる(NG)
      address.postal_code = '12A-4567'
      expect(address).to be_invalid
    end
  end
end

ちなみに、正規表現の読み方がわからない、という方はこちらの記事を読んでみてください。

他にも条件付きのバリデーションだったり、自作メソッドでバリデーションを行う場合(Railsがデフォルトで用意しているバリデーションを使わない場合)もテストを書いた方がいいでしょう。

class Person < ApplicationRecord
  # 18歳未満なら保護者氏名が必須
  validates :parent_name, presence: true, if: -> { age < 18 }
end

# テストコードは省略
class Order < ApplicationRecord
  # 秘密の商品はpremium顧客しか注文できない
  validate :customer_should_be_premium, if: -> { product.secret? }

  def customer_should_be_premium
    unless customer.premium?
      errors.add(:customer, :cannot_order)
    end
  end
end

# テストコードは省略

「でも、RSpecの解説書にバリデーションもテストしようって書いてあったんですが?」

ぎくっ。その解説書はもしかしてこれですか?

そうなんですよね。たしかにこの本ではバリデーションもテストした方が良い(と解釈できる)記述があります。

「こんなテストは役に立たない。モデルに含まれるすべてのバリデーションを確認しようとしたらどれくらい大変になるのかわかっているのか?」そんなふうに思っている人もいるかもしれません。ですが、実際はあなたが考えている以上にバリデーションは書き忘れやすいものです。しかし、それよりもっと大事なことは、テストを書いている 最中に モデルが持つべきバリデーションについて考えれば、バリデーションの追加を忘れにくくなるということです。

引用元: Everyday Rails - RSpecによるRailsテスト入門

僕はこの本の翻訳者ですが、この点については原著者のAaronさんと少し意見が異なっています。ただし、Everyday Railsはあくまで訳書なので、原著者の考えを最優先して上記の記述をそのまま載せています。

まとめ

繰り返しになりますが、「単純なバリデーションならテストを書かなくてもいい」というのは僕(伊藤淳一)の私見です。また、バリデーションのテストを書くことがまったくの無価値だとは言いませんし、学習目的でバリデーションのテストを書くのは全然アリだと考えています。

もしチームでRailsアプリケーションを開発しているのであれば、「単純なバリデーションに対してテストを書くべきかどうか」を議論して、チーム内の見解を統一することをお勧めします。

83
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
83
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?