Fun Done Run

yazawa's tech blog

Rails アプリにモデルスペックを導入する

はじめに

今回はRails でアプリケーションを作っている際に、モデルスペックを書くシーンがあったので、「どんなテストを書けば良いのか」という観点でまとめてみた。

目次

参考書籍

今回の記事はこちらの書籍を参考にまとめた (なるべく内容を丸写ししないようにまとめたつもりですが、ご指摘などあればコメントいただけると幸いです)

leanpub.com

モデルスペックを導入する時の手順

  1. 既存のモデルに対してモデルスペックを作る
  2. モデルのバリデーション、クラスメソッド、インスタンスメソッドのテストを書く

モデルスペックに含めるべきテスト

  1. モデルの状態が有効であること(有効なパラメータを与えて初期化等)
  2. モデルの状態が有効でないこと(Not Null などのバリデーションが失敗するようなデータ等)
  3. クラスメソッドとインスタンスメソッドが期待通りに動作すること

一言で言うと、「モデルが有効か無効か」と「各メソッドが期待通りの動きをすること」のテストを記述する。

モデルスペックの書き方

モデルスペックの大枠は以下のようになる。例えばEC サイトで製品を表すproduct モデルがあった場合、以下のようになる。

describe Product do
  it 'is valid with name, price' # 有効な場合のテスト
  it 'is invalid withuot name'   # 有効でない場合のテスト
  it 'is invalid withuot price'  # 有効でない場合のテスト
end
  • describe から始まり、Product モデルへのテストを記述することを宣言している
  • it の後に説明文を書くことで、何に対しての検証か、何を期待するかが明確になっている
  • 【重要】describe の後に続く「モデル名」 + it の後に続く「テストケース」が英語の文章になり、意味が通じるように記述する
    • Product is valid with name, price;

上の状態で bin/rspec コマンドを実行すると、 pending の状態のテストが3つ出来上がる。

1.モデルが有効な場合のテスト

有効であることを検証するためには、例えば以下のようにテストを記述する。

describe Product do
  it 'is valid with name, price' # 有効な場合のテスト
    product = Product.new(
      name: "spoon",
      price: 300
    )
    expect(product).to be_valid
  end
end

expect(product).to be_validproduct.valid? == true を検証しているイメージを持つと、わかりやすい。

2.モデルが有効でない場合のテスト

有効でない場合のテスト、すなわちバリデーションエラーになるようなテストを実行したい場合には、例えば以下のように記述すれば良い。

describe Product do
  it 'is invalid withuot name' do  # 有効でない場合のテスト
    product = Product.new(
      name: nil,
      price: 300
    )
    product.valid?
    expect(product.errors[:name]).to include("can't be blank")
  end
end

product.errors[:name] は 「Product モデルのname 属性に関するエラーメッセージの配列」を返し、その配列が 「"can't be blank"という文字列を含んでいるか」を検証している。product.errors[:name].include?("can't be blank") == true を検証しているイメージを持つと、わかりやすい。

3.クラスメソッドとインスタンスメソッドが期待通りに動作するテスト

インスタンスメソッドのテスト

例えばインスタンスメソッドに以下のようなメソッドがあったとする。

class Product
  def equals?(product)
    (self.name == product.name) && (self.price == product.price)
  end
end

この場合は、モデルが有効な場合や無効な場合と同様に以下のように書ける。

describe Product do
  it 'returns true given product with same name and same price'
    one_product = Product.new(
      name "banana",
      price 500
    )
    another_product = Product.new(
      name "banana",
      price 500
    )
    expect(one_product.equals?(another_product)).to eq true
  end
end

スコープとクラスメソッドのテスト

例えば、製品を金額で条件を指定して検索するスコープが以下のように定義されているとする

scope :upper_than, ->(price) {
  where("price >= ?", price)
}

この場合にモデルのテストを書くとしたら以下のようになる(大枠のみ)

RSpec.describe Product, type: :model do
  it "returns products that match the search price"
    # Product オブジェクトをいくつか作る
    # 検索条件を渡してスコープを実行し、一致するオブジェクトを含むか含まないかをテストする
    # …
    # 失敗するテストも追加する
    # 例えば、検索条件として不適切な値を入れた時に、検索結果が0件の場合のテストなど
end

まとめ

  • 今回はRSpec を使った書き方も含め、モデルスペックを書くときの指標をまとめてみた
  • Rails だけじゃなく、全ての単体テストを書く時にも参考になると思うので、他のアプリでも指標として使いたい
  • システムスペックの指標も自分なりに整理したいので、次回はシステムスペックについて掘り下げてみたいと思う