2520


ローカルの環境で単体でのテストの場合はパスするのですが、  
一括でのテストの場合に、たまにエラーになるケースがあります。

環境は下記のような感じです。

  * ruby 2.1.3
  * rails 4.1.8
  * rspec 3.1.0
  * rspec-core-3.1.7
  * capybara-2.4.4
  * poltergeist-1.5.1
  * phantomjs 1.9.8

`config.order = 'random'`にしています。  
randomでない場合に一括実行すると必ずエラーになるのでrandomにしています。

テストコードは、具体的には以下。その他にも同じようにランダムにエラーになる箇所があります。

[code]

      scenario '顧客を新規追加する', js: true do
        visit new_customer_path
    
        tab = first('#new_customer') # Ambiguous match by rack_test
        within tab do
          fill_in 'customer_name', with: 'customer'
        end
    
        expect {
          click_button I18n.t('helpers.submit.create')
        }.to change(Customer, :count).by(1)
      end
    
[/code]

エラー内容は、他のケースでも以下のような感じです。

![画像の説明をここに入力](https://i.stack.imgur.com/gM5qN.png)

このようなケースに陥った方アドバイスをお願いします。

その他、色々と調べた結果、Ajax使用箇所では、以下のようなスニペットを用いたりしてみましたが・・

[code]

    module WaitForAjax
      def wait_for_ajax
        Timeout.timeout(Capybara.default_wait_time) do
          loop until finished_all_ajax_requests?
        end
      end
    
      def finished_all_ajax_requests?
        page.evaluate_script('jQuery.active').zero?
      end
    end
    
[/code]

rspec_helperは以下の通りです。

[code]

    ENV['RAILS_ENV'] ||= 'test'
    require File.expand_path('../../config/environment', __FILE__)
    require 'rspec/rails'
    require 'email_spec'
    require 'shoulda-matchers'
    require 'capybara/rails'
    require 'capybara/rspec'
    require 'capybara-screenshot'
    require 'capybara-screenshot/rspec'
    require 'capybara/poltergeist'
    require 'capybara/webkit'
    require 'devise'
    require 'webpay/mock'
    require 'pundit/rspec'
    require 'simplecov'
    
    Capybara.register_driver :poltergeist do |app|
      Capybara::Poltergeist::Driver.new(app, inspector: true, js_errors: false, timeout: 60, debug: false)
    end
    Capybara.javascript_driver = :poltergeist
    Capybara.default_wait_time = 5
    Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
    
    ActiveRecord::Migration.maintain_test_schema!
    DEFAULT_HOST = Settings.host
    
    RSpec.configure do |config|
      if ENV['CI'] == 'true'
        config.filter_run_excluding webpay: true
      else
        config.filter_run_excluding slow: true, omit: true
        config.filter_run_excluding service: true
      end
    
      config.before :suite do
        FactoryGirl.reload
        OmniAuth.config.test_mode = true
        WebMock.disable_net_connect!(allow_localhost: true)
        DatabaseCleaner.clean_with(:deletion)
        load Rails.root.join('db', 'seeds.rb')
      end
    
      config.before :each do |example|
        allow_any_instance_of(Account::Setting).to receive(:geocode).and_return([1,1])
        if example.metadata[:js]
          DatabaseCleaner.strategy = :deletion #
        else
          DatabaseCleaner.strategy = :transaction
        end
        DatabaseCleaner.start
      end
    
      config.after :each do |example|
        page.driver.reset!
        DatabaseCleaner.clean
        if example.metadata[:js]
          load Rails.root.join('db', 'seeds.rb') 
        end
      end
    
      config.after :suite do
      end
    
      config.infer_spec_type_from_file_location!
      config.order = 'random'
    
      ## Capybara
      config.include Capybara::DSL
      Capybara.default_host = 'http://' + DEFAULT_HOST
      # FactoryGirl
      config.include FactoryGirl::Syntax::Methods
      # EmailSpec
      config.include(EmailSpec::Helpers)
      config.include(EmailSpec::Matchers)
      # Webpay
      config.include WebPay::Mock::WebMockWrapper
      # Devise etc..
      config.include Devise::TestHelpers, type: :controller
      config.extend ControllerMacros, type: :controller
      config.include OmniauthMacros
      config.include FeatureMacros, type: :feature
      config.include WaitForAjax, type: :feature
    end
    
[/code]

＝＝＝＝ 補足 ＝＝＝＝＝＝＝

`config.after(:each, js: :true) { wait_for_ajax }`をspec_helper.rbに追記。  
かつ、`config.order = 'random'`をコメントアウト。

`spec spec/feature/`のみテスト。

[code]

      scenario '顧客を新規追加する', js: true do
        visit new_customer_path
        wait_for_ajax
    
        tab = first('#new_customer')
        within tab do
          fill_in 'customer_name', with: 'customer'
        end
    
        expect {
          click_button I18n.t('helpers.submit.create')
          wait_for_ajax
        }.to change(Customer, :count).by(1)
      end
    
[/code]

上記のテストコードでエラーとなる。（61 examples, 1 failures）

しかし、再度実行すると、エラーとなります。（エラーになるかどうかは不定）。

========= 追記 ============

別件で以下のエラー対策にtest.rbを編集しました。

`Circular dependency detected while autoloading constant`

[Circular dependency detected while autoloading constant
を防止する](http://qiita.com/kysnm/items/53fe43071be8ddb3800a)

  * config/environments/test.rb 
    * config.allow_concurrency = false

すると、エラーの様相が変わりました。

![画像の説明をここに入力](https://i.stack.imgur.com/gM5qN.png)

Capybaraのスクリーンショットなどが取得できるようになりました。  
エラー内容もmouse event のクリックができないなどになりました。

  * 上記の設定変更はAjax使用時の並行処理発生を防止する
  * WaitforAjax絡みで、DBへの並行接続が問題になっているという情報があった（過去に見たのでリンク不明）

なので、今まで出ていたエラーの原因はDB絡みと仮定します。  
しかし、現段階でもランダムにエラーは発生します。

エラーの中にmouse event clickに関するエラーも発生したので、  
設定変更以降にエラーになる原因は、タイミングなのかなと思います。  
試しにWaitForAjaxで、`sleep`を入れて試してみました。

[code]

    module WaitForAjax
      def wait_for_ajax
        Timeout.timeout(Capybara.default_wait_time) do
          loop until finished_all_ajax_requests?
        end
        sleep 0.5
      end
    
      def finished_all_ajax_requests?
        page.evaluate_script('jQuery.active').zero?
      end
    end
    
[/code]

さらに、wait_for_ajaxを以下の箇所に追加してみました。

  * visitの後
  * clickなどの処理すべて

すると、エラーの発生頻度が低くなりました（数十回程度試した結果ですが。）  
推測ですが、以下の要因が混在していたのだと思います。

  * Ajaxによる並行接続でのDBエラーと
  * Capybaraの画面操作のタイミング

今までは全体のテストの際に数回に１回程度エラーが出ていましたので、  
少しの間様子見をし、また経過を追加します。

追加した箇所の例を記載しておきます。  
以下のコードの`find_button(I18n.t('posts.update_state.finish')).click`の後の  
`wait_for_ajax`がなければ、  
`undefined method`perform_deliveries' for nil:NilClass`が発生します。  
（タイミングによりますが）

[code]

        scenario 'example', js: true do
            post.accept!
            visit post_path(token: post.token)
            wait_for_ajax
    
            ActionMailer::Base.deliveries.clear
            find_button(I18n.t('posts.update_state.finish')).click
            wait_for_ajax
    
            within '#update-finished-modal-content' do
              check('post_send_mail')
              find_button(I18n.t('posts.modal.update_finished.submit')).click
            end
            find_button(I18n.t('helpers.submit.continue')).click
            wait_for_ajax
    
            expect(open_last_email_for(user.email)).to be_delivered_to user.email
          end
    
[/code]

=========== 追記 ===============

ローカル環境とCI上（外部サービス）で２０回程度テストして、１回だけ以下のエラーが出ました。

  * undefined method `perform_deliveries' for nil:NilClass

エラー発生の頻度は下がりましたが、根本的に解決とはいえないのですし、wait_for_ajaxを使わないとpoltergeistのJSテストが成り立たないというのも変な感じです。

poltergeistによるRspecのJSテストについて皆さんがそのような対応をしているとは思えないので、  
引き続き回答をお待ちしています。

ちなみにCapybara-webkitに変更しても同じような箇所でエラーになります。  
Capybara-webkitの場合、現在の設定でもエラーが出ます。

Webkitでのエラーの内容例

  * `Capybara::Webkit::InvalidResponseError:  
Unable to load URL: http://127.0.0.1:63380/users/reservations because of error
loading http://127.0.0.1/mypage: Unknown error`

  * undefined method `perform_deliveries' for nil:NilClass


CC BY-SA 3.0
2014-12-08T13:59:21.597
463
2014-12-29T12:08:07.190
2014-12-29T12:08:07.190
33
512
question
ruby ruby-on-rails テスト rspec
Rspecのフィーチャーテストが失敗したりしなかったり


回答というわけではないですが、自分も似たような問題にぶつかったので、問題を特定したやりかたを共有します。

まず、問題が出るシード値を特定します。

Rspecを実行すると以下のように出力されます。

[code]

    Randomized with seed 12130
    
[/code]

これがSpecの実行順を決める乱数のシード値なので、実行順に依存して失敗するのであれば、同じシード値を与えれば失敗も再現出来るはずです
（再現したりしなかったりするのであれば実行順以外の要因がある可能性があります）。 テストに失敗するシード値を絞り込んだらそれをメモします。

次に、失敗の原因となるファイルを特定します。 `rspec {対象ファイル相対パス}`で特定のファイルだけテストを実行出来ます。 `rspec
{ファイル1} {ファイル2}`と複数指定も出来ます。ディレクトリ指定で配下のファイルを丸ごと指定することも出来ます。
順番で失敗するのであれば、特定のファイルが原因となる可能性が高いので、失敗するテストが含まれるファイルと失敗の原因が含まれるファイルの候補を指定してテスト実行し、原因を絞り込みます。

[code]

    bundle exec rspec {失敗するテストが含まれるファイル} {失敗の原因になりそうなファイル群} --order=random:{シード値}
    
[/code]

自分の場合は上記を実行して、二分探索を行い、原因となるファイルを絞り込みました。

_補足: `rspec {ファイルパス}:{行番号}`で行番号も指定出来ますので、ケース単位で絞り込む場合は試してみてください。


CC BY-SA 3.0
2014-12-17T03:14:12.377
1852
2014-12-17T05:46:46.317
2014-12-17T05:46:46.317
2213
2213
463
answer


js:
trueを付けるとJSの処理が終わる前にRSpec側が先に進んでしまい、本来通るはずのテストがパスしない、もしくはタイミングによってパスするときと失敗するときがある、ということは僕も良く体験しています。

トラブルシューティングの方法としては以下のようなことを良く試します。

まず、JSの処理を起動するアクション（例: リンクやボタンのクリック）の直後に `sleep 3`
など十分長さのsleepを入れてみます。（RSpec側のコードを変更）

これで毎回テストがパスするようならRSpec側を適当にsleepさせる必要があると言うことです。
sleep秒数が長すぎるとテストの実行に時間がかかるので、0.5や1など、テストが失敗しないレベルの短い秒数を指定してください。

sleepを長くしてもテストが失敗するのであれば、問題は別のところにあると思われます。 RSpecやRails側のコードに
`Rails.logger.info "xxx"`
のようにログ出力を埋め込んで処理が実行されたタイミングやメソッドの戻り値、DBの値などを出力してみてください。

そしてテストを何度か実行して、失敗したときの `logs/test.log` を確認してください。
ログに出力された情報からテストが落ちた原因が推測できるかもしれません。

あと、DatabaseCleanerの設定を `DatabaseCleaner.strategy = :deletion` から
`DatabaseCleaner.strategy = :truncation` に変えてみるとどうでしょうか？
テストのたびにDBが完全にリセットされるので、タイミングによって失敗する、ということがおきにくくなるかもしれません。

僕自身はtruncationを使っています。（use_transactional_fixturesもfalseにしています） 設定のサンプルはこちらです。

<https://github.com/JunichiIto/rspec_rails_4/blob/08_features/spec/spec_helper.rb#L50>

wait_for_ajax は僕もネットで見たことがありますが、実際には使ったことがありません。 原始的ですがsleepをよく使っています。（JSの処理=
Ajaxとは限らないので）

allow_concurrencyという設定も変更したことはないです。
このあたりはあまり凝ったことをせず、シンプルな状態でデバッグを進めていく方がいいんじゃないかなと思います。

簡単に原因は見つからないかもしれませんが、こうした情報を参考にしながら試行錯誤してみてください。


CC BY-SA 3.0
2014-12-25T02:08:45.180
2520
2014-12-25T02:08:45.180
85
463
answer