Rspec에서 모의를 사용하여 테스트를 작성하겠습니다.

18548 단어 RSpec

이 기사에서 쓰기


  • 첫번째 코드에서 어떻게 변화했는가?
  • 모의 테스트를 싫어하는 인간이 어떻게 테스트를 극복했는지
  • double
  • allow_any_instance_of
  • allow


  • 이 기사에서 쓰지 않는 것


  • gem 사용에 대한 자세한 설명
  • Amazon Product Advertising API 5.0.에 대한 자세한 api 사양
  • rake 작업의 spec 좋은 (적절한, or 올바른) 쓰기

  • 제일 전하고 싶은 일


  • 테스트를 작성하는 것은 더 나은 코드를 작성하기위한 지름길

  • 테스트하려는 코드



    rake 작업입니다. 코드는 실제 코드에 비해 단순화되었습니다.
    Amazon Product Advertising API 5.0을 요청하여 제품을 생성한다는 내용.
    지금 현재 (2020/07) Ruby용 공식 SDK 없기 때문에,
    htps : // 기주 b. 코 m / 하카넨 사리 / め 쿠 m
    이번에는 vacuum이라는 gem을 사용하여 구현됩니다.

    lib/tasks/item.rake
    namespace :item do
      desc 'amazon apiで取得した商品を登録する'
      task insert_from_amazon_search_item: :environment do
        # request作成
        request = Vacuum.new(marketplace: 'JP',
                             access_key: 'xxxxxxxxxx',
                             secret_key: 'yyyyyyy',
                             partner_tag: 'hoge_fuga')
        # search_items を呼び出す
        response = request.search_items({
          search_index: 'All',
          market_place: 'www.amazon.co.jp',
          resources: [
            'ItemInfo.Title',
            'Offers.Summaries.LowestPrice'
          ],
          keywords: 'ねこまみれ'
        })&.to_h
    
        response['SearchResult']['Items'].each do |item|
          new_price = item['Offers']['Summaries'].detect do |summary|
            summary['Condition']['Value'] == 'New'
          end
    
          # itemの作成
          Item.create name: item['ItemInfo']['Title']['DisplayValue'], price: new_price['LowestPrice']['Amount']
        end
      end
    end
    

    spec/lib/tasks/item_spec.rb
    
    require 'rails_helper'
    require 'rake'
    
    RSpec.describe 'item:insert_from_amazon_search_item' do
      before(:all) do
        @rake = Rake::Application.new
        Rake.application = @rake
        Rake.application.rake_require 'tasks/item'
        Rake::Task.define_task(:environment)
      end
    
      before(:each) do
        @rake[task_name].reenable
      end
    
      context 'when valid response returns,' do
        let(:task_name) { 'item:insert_from_amazon_search_item' }
        it 'creates new item.' do
          # ど、どうしたらいい・・・・・・
          expect do
            @rake[task_name].invoke
          end.to change(Item, :count).by(1)
        end
      end
    end
    

    자신이 테스트를 작성하기가 어렵다고 느낀 부분


  • search_items는 Vacuum 인스턴스에 대해 실행할 수 있습니다.
  • search_items 에서 item을 참조하는 데 사용 to_h htps : // 기주 b. 이 m/하카넨사리/ゔぁくうm/bぉb/마s테 r/ぃb/ゔぁくうm/레 s폰세. rb#L44Array#to_h 에 정의되어 있는 메소드. 어떻게 테스트로 대답하도록 하면 좋을까?

  • 어렵다고 느끼는 것은 코드를 쓰는 법이 이상하다는 것을 깨닫는다.



    논리를 수정할 때 의식이 있음


  • 외부의 Vacuum::Response를 호출하는 부분의 로직은 class로 해, 그 안에서 각각의 외부에 의존한 메소드를 호출하게 한다

  • 이것에 의해, 테스트에서는 mock를 사용해, 그 class의 인스턴스에 대해서 응답하는 값을 설정하기 쉬워졌다.

    class는 어느 디렉토리에 넣어야 하는가.Vacuum와 같이, 외부 서비스에 관한 로직이었으므로, app/libs/amazon_product_api_caller.rb 이하에 작성했습니다.

    app/libs/amazon_product_api_caller.rb
    class AmazonProductApiCaller
      def initialize
        @request = amazon_api_request
      end
    
      def search_items
        @request.search_items({
          search_index: 'All',
          market_place: 'www.amazon.co.jp',
          resources: [
            'ItemInfo.Title',
            'Offers.Summaries.LowestPrice'
          ],
          keywords: 'ねこまみれ'
        })&.to_h
      end
    
      private
    
      def amazon_api_request
        return Vacuum.new if Rails.env.test?
    
        Vacuum.new(marketplace: 'JP',
                   access_key: 'xxxxxxxxxx',
                   secret_key: 'yyyyyyy',
                   partner_tag: 'hoge_fuga')
      end
    end
    
    

    lib/tasks/item.rake
    namespace :item do
      desc 'amazon apiで取得した商品を登録する'
      task insert_from_amazon_search_item: :environment do
        # request作成
        amazon_product_api_caller = AmazonProductApiCaller.new
    
        # search_items を呼び出す
        response = amazon_product_api_caller.search_items
    
        response['SearchResult']['Items'].each do |item|
          new_price = item['Offers']['Summaries'].detect do |summary|
            summary['Condition']['Value'] == 'New'
          end
    
          # itemの作成
          Item.create name: item['ItemInfo']['Title']['DisplayValue'], price: new_price['LowestPrice']['Amount']
        end
      end
    end
    

    이런 상태가 되면 드디어 테스트에 다시 한번 마주합니다.
    참고로 해 주신 Qiita의 기사 감사합니다.
    사용할 수 있는 RSpec 입문·그 3 「제로로부터 알 수 있는 모의(mock)를 사용한 테스트의 작성 방법」

    spec/lib/tasks/item_spec.rb
    
    require 'rails_helper'
    require 'rake'
    
    RSpec.describe 'item:insert_from_amazon_search_item' do
      before(:all) do
        @rake = Rake::Application.new
        Rake.application = @rake
        Rake.application.rake_require 'tasks/item'
        Rake::Task.define_task(:environment)
      end
    
    
      before(:each) do
        @rake[task_name].reenable
        # 実際に返ってくるレスポンスと同じような値を用意する。
        response_hashed = {
          "SearchResult" => {
            "Items" => [{...}]}
        }
    
        # AmazonApiに関するモックを用意。(外部との難しい部分をうけおってくれるモックになる。頼むぞ!
        let(:amazon_api_double) { double('amazon api caller') }
        # Vacuum.new に対しては、こちらで用意したモックを返すようにする
        allow(Vacuum).to receive(:new).and_return(amazon_api_double)
        # こちらで用意したモック(Vacuumのフリをするモック)に対してsearch_itemsを呼べるようにする。
        allow(amazon_api_double).to receive(:search_items)
        # AmazonProductApiCaller のインスタンスに対してsearch_itemsが呼ばれたら、テストで用意した値が返るようにする。
        allow_any_instance_of(AmazonProductApiCaller).to receive(:search_items).and_return(response_hashed)
    
      end
    
      context 'when valid response returns,' do
        let(:task_name) { 'item:insert_from_amazon_search_item' }
        it 'creates new item.' do
          expect do
            @rake[task_name].invoke
          end.to change(Item, :count).by(1)
        end
      end
    end
    

    이렇게 무사히 테스트를 쓸 수 있었습니다.

    좋은 웹페이지 즐겨찾기