6
5

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 3 years have passed since last update.

【Rails API 入門】RSpec ①

Last updated at Posted at 2020-05-04

はじめに

RSpecとは、Railsのテストフレームワークの1つです。次のような理由から導入することが望ましい考えています。

  • 現場で一番使われている
  • 可読性の高い記述ができる
  • エラー発生を事前に防ぐことができ、結果的にテストを書く方がコスパがよい
  • テスト駆動開発(はじめにテストを書く)が注目されている

最終目標は、RailsでAPIを実装する際のRSpecの使い方を理解することです!
最後まで頑張って目を通して下さると幸いです!

(昨今のVueやReactなどのフロントエンドフレームワークの普及により、RailsはAPIモードとして、フロントエンドから送られたパラメータを処理し、その結果をJSONレスポンスとしてフロントエンドに返す役割を担うことが多い。)

皆さんがなるべくストレスなく理解できるよう、以下のように順を追って説明します。

  1. RSpecを使う上で事前に知っておくこと → この記事
  • モデルのバリデーションテスト(モデルスペック)→【Rails API 入門】RSpec ②(予定)
  • 単体クラスのテスト →【Rails API 入門】RSpec ③(予定)
  • 実装するAPIの処理、レスポンスのテスト(リクエストスペック)→【Rails API 入門】RSpec ④(予定)

RSpecを使う上で事前に知っておくこと

FakerFactoryBot

Faker とは

ランダムなデータを、実行するたびに作ってくれるものです。

RSpecでは毎回自分でランダムな値を用意するわけにはいかないので、次に説明するFactoryBotを含め、これらのgemを使用することが望ましいです。

Faker::Name.name
=> "Francesco Russel"

Faker::Name.name
=> "Junior Dickinson"

このような書き方で、ランダムなデータを毎回作ってくれます。
その他にも、様々な種類のデータを準備することができるので、詳細はFakerのGitHubで調べて見て下さい。

FactoryBot とは

ダミーのレコードを作ってくれるものです(こちらがGitHubページ)。

次のような、商品Productモデルのダミーレコード作成を例として説明します。

まずはどのようなダミーレコードを作るのかをspec/factories配下にファイルを作成して記述します。

spec/factories/products.rb
FactoryBot.define do
  factory :product do
    name { Faker::Food.fruits }
    # 略
  end
end

すると、次のような書き方をすることで

FactoryBot.build(:product) # Product.new(name: Faker::Food.fruits)と同じ

=> #<Product:0x0000000000000000
 id: nil,
 name: "Banana",
 created_at: nil,
 updated_at: nil>

のようなランダムなデータを作成することができます。
buildは、newを実行するProduct.new(name: Faker::Food.fruits)と同じ結果を得ることができるということです。

また同じようなやり方で、Product.create(name: Faker::Name.name)

FactoryBot.create(:product) # Product.create(name: Faker::Food.fruits)と同じ

=> #<Product:0x0000000000000000
 id: 1,
 name: "Lemon",
 created_at: Mon, 04 May 2020 07:30:41 UTC +00:00,
 updated_at: Mon, 04 May 2020 07:30:41 UTC +00:00>

のようにレコードを作ることができます。

パラメーターとしてハッシュ値を返したい場合は、

FactoryBot.attributes_for(:product) # { name: Faker::Food.fruits }と同じ

=>{:name=>"Pineapple"}

を使用することができます。

ちなみにそれぞれ、FactoryBot.build(:product)

build(:product)

FactoryBot.create(:product)は、

create(:product)

FactoryBot.attributes_for(:product)は、

attribute_for(:product)

と省略して記載することができます!(以降はこの省略形で説明します。

letbefore

let とは

変数を格納するような機能です。

これを使うことで可読性が上がるタイポに気づける等のメリットがあるので、できる限り変数を用意する時は、letを使うようにして下さい(詳しい理由はこちらを参考にして下さい)。

let(:product) { create(:product) }
=> product # productのインスタンス変数が作られる

ここでは、let直後の(:product)で変数productを定義し、{}内のcreate(:product)でランダムなレコードを作成しています(create(:product)はFactoryBotでランダムなレコードを作成するもので、先程解説した内容です)。

before とは

テストを行いたい処理の前に行っておきたい処理を書くものになります。

例えば、特定の商品(productレコード)を所有するUserhas_manyhas_oneリレーション)を事前に作成しておきたい場合は、テストを行う処理の前に

  before { create(:user) }

を記述することで、事前にUserを作成しておくことができます。

subject

subject とは

テストしたい処理を明示的に書くもので、テストの可読性が向上するものです。

明確に「〇〇処理」を行うテストだよ!と、「主題」(subjectの英語訳)のようにアピールするような役割を持つものです。
後ほど具体例を交えながらの解説を行いますので、ひとまず、テストしたい処理をsubjectに書くと覚えて下さい。

subject { product.valid? }

ですのでこれは、{}内のproduct.valid?テストしたい処理であり、わざわざsubjectとして切り分けて書くことで、「このテストはproduct.valid?の処理をテストしようとしているんだな!」とレビューする人が判断できるようになります(逆に言えば、subjectがなくても同様のテストを実装することは可能なのです)。

ここで言うvalid?productインスタンスのバリデーションを実行するメソッドであるので、subjectを使うことで明示的に、「このテストではバリデーションのテストを行いたいのか!」と解釈することができます。

expect

expect とは

expectの英語訳が「期待する」であるように、テストを行うことで期待される処理を書くところです。

例えば、

expect(product.name).to eq "Banana"

があるとすると、expect直後のproduct.name"Banana"eq(等しい)ことを期待する、確認するという処理になります。

処理結果としてはtruefalseを返し、trueであればテストの成功falseであればテストの失敗(期待される結果ではない)ということになります。

eqマッチャーと呼ばれ、eq以外にも様々なものがあります。詳しくはこちらを確認して下さい。

具体例

商品モデル(Product)のnameカラムのバリデーションのテストを想定しましょう。

次のようなバリデーションが設定してあるとします。

app/models/products.rb
class Product < ApplicationRecord
  validates :name, presence: true
  # 略
end

今であれば、何となく、次に示す**Productモデルのバリデーションテスト**の内容を理解できるのではないでしょうか?

spec/models/product_spec.rb
require "rails_helper"

RSpec.describe Product, type: :model do
  describe "バリデーションの確認" do
    subject { user.product.valid? }

    before { create(:user) }

    context "正常なレコードが作られる時" do
      let(:product) { create(:product, user: user) }

      it "商品を登録することができる" do
        expect(subject).to be true
      end
    end
  end
end
RSpec.describe Product, type: :model do

まず、RSpec.describe後ろのProductは、Product モデルを対象としてテストするよという意味合いです(単体クラスのテストではProductクラスのテストという意味になります)。

その後の、type: :modelモデルのテストを行うよ!という意味で、
APIのテストであるリクエストスペックの場合は、type: :requestのように書き、ここではテストの種類を明示します(モデルとは関係のない単体クラスのテストの場合は省略します)。

その次の行は、

describe "バリデーションの確認" do

と書かれてあります。

このdescribeは、テストがどういう内容か?を補足する役割を担っており、その他にもcontextit が存在します。それぞれ、

  • describeテストの対象や行う処理を表す記述
  • contextテストの条件を示す記述
  • itテスト結果として期待されるものを示す記述

を書く必要があります。

これらのテスト内容を補足する役割がある箇所を抜き出すと、

  describe "バリデーションの確認" do

    context "正常なレコードが作られる時" do

      it "商品を登録することができる" do

      end
    end
  end

となり、上から読み解いていくと、

  • テストの対象や行う処理"バリデーションの確認"で、テストの条件として"正常なレコードが作られる時"を想定し、期待される結果商品を登録することができるであるテスト

の意味になります!

このような理由で、RSpecでは**何をテストしようとしているのか?**を、視覚的に理解しやすく、可読性が高いと言われています。

少し前に戻り、先程説明したdescribe "バリデーションの確認" doの次の行、subjectです。

subject { user.product.valid? }

このようにsubjectがあることで、このテストではuser.product.valid?の処理をテストしたいんだ!
と一目で理解することができます。

ちなみにこの処理はこの時点では実行されません。実行のタイミングは後でやってきます。

そして、その次の処理、

before { create(:user) }

beforeは、テストを行いたい処理の前に行っておきたい処理を書くものでしたね!
ですので、このタイミングでuserインスタンスが作られます(UserモデルのFactoryBotが実装されてある想定)。

次の

let(:product) { create(:product, user: user) }

letなのでproductのインスタンス変数を作っていますが、

create(:product, user: user)

の書き方は、この記事で説明した内容より少し発展したFactoryBotの使い方です。

これは、先程before { create(:user) }で作られたuserを親として持つproductインスタンスを作る、という意味になります。
リレーションが関わるレコードを作るのは頻出なので、合わせて覚えていただけると良いと思います!

そして最後にitの中身を解説します。

it "商品を登録することができる" do
  expect(subject).to be true
end

RSpecでは、itブロック内に、テストとして確かめたい動作コードを実装し、it内が成功すればテストの成功、失敗すればテストの失敗、となります。

expect(subject).to be true

は先程のexpectとはで説明した通り、

  • subjectの実行結果が、trueである(beマッチャー)か

を結果として期待しています。

それではsubjectの実行とは何か?それは、先程定義した、subject { user.product.valid? }の実行、つまり、

user.product.valid?

であり、user.productのバリデーション結果がtrueなのか?それともfalseなのか?になりますね!

これでやっと、バリデーションのテストが完了したことになります。
(ここまで、大変お疲れ様でした!)

ちなみに、実行結果、つまりターミナルでの出力は、

terminal
Product
  バリデーションの確認
    正常なレコードが作られる時
   商品を登録することができる

Finished in 0.12345 seconds (files took 1.23 seconds to load)
1 example, 0 failures

のようになります。

このターミナルの表示を見ると一目でテスト内容が分かり、

  • Productモデルのバリデーションの確認を行っており、正常なレコードが作られると、商品を登録することができる!

というテスト結果を確認することができます。
(このターミナル表示は.rspecファイルに--format documentationを書いて設定しており、出力結果の見た目が良くなるようにしています。)

もしも、describe "バリデーションの確認"の記述を省略している場合は、

terminal
Product
  正常なレコードが作られる時
   商品を登録することができる

のような出力結果となり、なんとなくは分かりますが、具体的なテスト内容が分かりません。

このように必要に応じてdescribecontextを利用し、テスト結果を誰が見ても分かる状態にするのが理想です!

終わりに

RSpecははじめは覚えることが多く、難しいと感じますが、繰り返し実装を行っていくと、頭の整理が付きやすく、スラスラ書けるようになります。みなさんがそのようになれるよう、この記事も改良していければと思います!

近日、【Rails API 入門】RSpec ②〜④を記事として書きます!頑張ります!

参考

6
5
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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?