バイセル Tech Blog

バイセル Tech Blogは株式会社BuySellTechnologiesのエンジニア達が知見・発見を共有する技術ブログです。

Rspecの実装について

テクノロジー戦略本部の稲川です。

 

今日は私がRspecやrailsを触り始めてから10年以上たち

長年Rspecを触りながら考えていたことを記事にさせていただきます

 

 

1.Rspecとは

Rubyで作られたライブラリ(Gem)であり

Ruby on Railsのフレームワークで実装された

プログラムの振る舞いを記述するためのドメイン特化言語(DSL)を提供するフレームワーク

 

Pythonのunittestや、JavaのJUnit、C#などの MSTestなどと同じように

機能要件をテストコード実行で担保できるようにする

いわば、システムのメンテナンスを効率的にするためのもの

 

2.なぜ、Rspecを使用するのか

  • 開発効率をあげるため
    • 画面でポチポチして確認する工数を削減し効率的にテストを行えるようにするため

  • 仕様(業務要件)の担保をspecでカバーすることでテストの漏れを少なくするため

    • 品質担保を確実にすることでより安全なシステムの運用保守を行えるようにする

    • 実装ロジックのデグレを実装時に把握できようにすることによりUT、ST時の不具合を少なくする

  • 上記、2つにより開発時の心的負担を減らすため(安心して安全なシステムを保守、運用できるようにするため) 

3.Rspec導入で可能なこと

  • 仕様の担保(テスト)を自動化することにより効率的な保守開発が可能

  • 仕様変更対応時に影響範囲の把握がだいたい可能

    • ただし、マイクロサービスの場合、Mock対象のAPIの挙動が変更した場合はこの限りではない

  • 仕様変更対応時に影響範囲の把握しやすくなることによりより正確な工数の見積もりが可能

  • リファクタリングが可能

    • コアロジックのTOPに対してのspecがあった場合に限る

    • コアロジックのTOPに対するspecがなく、細かいspecだけの場合はどうなるか(以下、デメリット)

      • コアロジック全体の仕様を担保していないため不足の事態(バグ)に対応するのに工数がかかる

      • 保守開発を行う上で自動化したテストの信頼性に欠けるため、テストの工数がかさむ

 

4.Rspec実装時の進め方(概論)

優先度の高いものから順に以下を記載

 

describeとcontextをきる 

  • テスト設計に該当

  • 実装者は有識者に上記観点で仕様を満たせるか(仕様を担保できるか)確認をとる

  • レビュアーは上記、テストの観点で仕様を満たせるかレビュー時に確認する

beforeやletでテスト実施に必要な前提条件を作成(ここまでできればテストの8割は完成) 

  • テスト実施(前提条件作成)に該当

subject or before or it でテスト実施+ itの中で検証内容の確認を行う

  • テスト実施+想定結果の確認に該当

spec追加当初はデバッグして以下を確認すること 

  • 前提条件となるデータが想定したデータになっていること

  • 振る舞いの検証内容があっていること

  • ファイル単位で実行してオールグリーンになること

  • 全体実行してオールグリーンになること

 

5.Rspec実装時の進め方(各論)

specの導入箇所の見極め方

基本はコアロジックのTOPに対してinputの条件ごとにoutputの振る舞いの検証を行う

(コアロジックがどこにあるかを見極めることが重要)

 

  • コアロジックのTOPに該当する箇所に対してspecを追加

    • inputに対するoutputの確認が行える一番TOPクラスにてspecでの確認を追加

    • inputのチェックの場合-> request_paramsの specを追加

    • APIの場合-> controllerのrequest specを追加

 

describeとcontextの切り方について

describe (振る舞いの対象)
  • controllerのspecの場合

    • アクションごとにdescribeをきる

      • controllerのspecの場合

        • アクションごとにdescribeをきる

          • # instance methodの前に#をつける -> HTMLのDOMをid指定するときと同じ
            describe '#create' do
            end

 

  • 上記以外の場合

    • class methodの場合(例:class method名: suggest)

       # method名の前にドット(.)をつける -> HTMLのDOMをclass指定するときと同じ
      describe '.suggest' do
      end
    • instance methodの場合

      • # instance methodの前に#をつける -> HTMLのDOMをid指定するときと同じ
        describe '#create' do
        end

 

context (振る舞いの対象の条件)
  • request spec(APIとなるcontrollerのspec)の場合

    • 例)

      Rspec.describe UsersController, type: :request do
        describe '#create' do
          context 'success' do
          end
        
          context 'failed' do
          end
        end
      
        describe '#show' do
          context 'success' do
          end
        
          context 'not found' do
          end
        end
      end

 

  • request paramsの場合(一般的にはmodelのvalidationのcontextと切り方は同じ)

    • 例)

      Rspec.describe UserRequestParams do
        describe '#validate!' do
          context 'valid' do
            # この中に細かい条件があった場合にはその条件毎にcontextきる
          end
          
          context 'invalid' do
            # この中に細かい条件があった場合にはその条件毎にcontextきる
          end
        end
      end

 

  • 上記以外の場合

    • 例)

      Rspec.describe UserService do
        describe '#method name' do
          context '正常系のコンテキスト名' do
          end
          
          context '正常系じゃないコンテキスト名' do
          end
        end
      end

 

subject、before、let、it の実装する順番や実装内容について

 

  • 実装する順番

    • subject → before → let → itの順で実装

 

  • 実装例)

Rspec.describe UserController do
  describe '#create' do
    context 'success' do
      # subjectのaliasをきる
      subject(:create_result) { 振る舞いの検証対象 }
      
      before do
        * 振る舞いの検証を行う前に必要な前提条件をここに実装
        * 特にFactoryBotでcreateしたものについてはここに入っていること
           (理由)入っていないと実ロジックからspecの it ブロック(検証内容)に戻ってきたときに
                     DBにあるはずのデータがないことになってしまうため
      end
      
      let(:created_users) { create_list(:user, 2) }
      let(:expected) do
        レスポンスの戻り値をここで作る
      end
      
      it do
        # APIのレスポンスが期待通りになっていることを確認(subject or create_resultの結果を確認)
        # Rspecでは一度subjectで実行された結果はitブロック内でキャッシュされる
        expect(create_result).to eq expected

        ## 以下、(SSのspecは特に)DBの確認内容
        # レコードの件数確認
        expect(User.count).to eq (created_users.count + 1)

        # DBにinsertされたデータが期待通りになっていること
        # insertしたテーブルの件数分以下を追加
        expect(User.last).to have_attributes(
          first_name: '氏名(姓)',
          last_name: '氏名(名)'
        )
      end
    end
  end
end 
it ブロックでの検証内容について

 

  • it ブロックで1行で書く場合(subjectを使った場合)

  • request spec(APIのspec)の場合

    • 共通で確認する内容(output の確認)

      • レスポンスの確認

      • HTTP statusの確認

      • エラーの確認(raise_errorでエラーになったclass名を確認)

    • DBに対して何かしらのアクションをするAPIの場合

      • insertする場合

        • 個数が想定通りの数になっていること

        • insertしたレコードが想定通りの値でinsertしていること

      • deleteする場合

        • 個数が想定通りの数になっていること

        • 想定したレコードがなくなっていること

 

  • 上記以外の場合

    • レスポンスの確認(output の確認)

 

shared_contextとshared_exampleの使い方について

spec実行時に前提条件や検証内容が同じである場合に共通化する場合には以下を使用

(ファイル単位で切り出すことは可能、ただし、その場合には切り出したファイルを requireすること)

 

 

 

  • Rspec.describe UserController do
      describe '#create' do
        shared_context 'prepare' do
          before { ここに前提条件を書く }
      
          let(:expected) { result }
        end
    
        shared_example 'OK' do
          let(:result) { 成功した場合のレスポンス }
      
          it { is_expected.to eq expected }
        end
    
        shared_example 'NG' do
          let(:result) { 失敗した場合のレスポンス }
      
          it { is_expected.to raise_error expected }
        end
    
        context 'success' do
          include_context 'prepare'
          it_behaves_like 'OK'
        end
        
        context 'failed' do
          include_context 'prepare'
          it_behaves_like 'NG'
        end
      end
    end
    

 

バイセルテクノロジーズではエンジニアを募集しています。

もし興味をお持ちいただけましたらぜひご連絡下さい!

 

6.参考資料

スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)

https://magazine.rubyist.net/articles/0021/0021-Rspec.html#rspec-%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B