Ruby勉強会を開催しました(2016#02)

今年も2回目の勉強会を開催しました。
今回の参加者は11名!
徐々に社内のRuby人口が増えつつあります。mrubyの勉強を中心にしてもらえるメンバも加わりました。
流行?のIoTの分野でもRubyは浸透していくと思いますので、mrubyについても積極的に取り組んでいきたいですね。

一方グループウェアの方は、Capybara, RSpec, DatabaseCleaner, FactoryGirl を使って遅ればせながらテストコードの実装を始めました。
リファクタリングでコードを見直すときも都度テストを実行することで、修正ミスをすぐに見つけることができので、思い切った変更も行ったりしています。

現在テスト周りで設定している内容を少しご紹介させていただきますが、これがベストではないと思いますのでご了承ください。

対象のテストはmodelの単体チックなテストと、UIを操作して行うテストfeatureの2種類です。
controllerのテストも実装していたのですが、ActiveRecordによるデータ取得(検索)の結果を一部正しく検証することができませんでした。
そんなこともあり、controllerのテストは早々にあきらめてしましました。基本UIのテストが通ればOKと判断できるようにしたいと思います。

まず、事前にデータベースに初期のデータを流し込みますが、これはseedで行っています。
将来的には手段を変更する必要性が出てくるかもしれませんが・・・

以下のようなコードをspec_helperに追加しています。

# spec_helper.rb

RSpec.configure do |config|
  :
  config.before(:suite) do
    Rails.application.load_seed
  end
  :

beforeの引数に :suite を指定していますので、すべてのテストがロードされた後、テスト(example)実行前にseedが実行されます。
改めてRSpecのドキュメントを見てみると、:each, :all がそれぞれ :example, :contextに変わっているようですね。
:each, :allはスコープのエイリアスとなっており、使うことは問題ないようです。

RSpec Core 3.4のドキュメントから引用すると、hookの順序は次のようになるようです。

before :suite
before :context
before :example
after  :example
after  :context
after  :suite

続いて DatabaseCleaner ですが次のように設定してみました。

# spec_helper.rb

  :
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
  end
  config.before(:example) do
    DatabaseCleaner.start
  end
  config.after(:example) do
    DatabaseCleaner.clean
  end
  :

RSpecのgemのバージョンは3.0.4だったのですが、:exampleという指定も可能でした。というかむしろこちらが標準になっているようでした・・・。

あまり必要性を感じていない部分もあるのですが、FactoryGirlも使っています。
ここで困った問題が発生しました。各テスト(feature)の実行時に、background(:each)の中で、FactoryGirlを利用してデータベースに登録したはずの データがテスト(example)の中で取得できなかったのです。

結局、feature毎に実行されるbackgroundとscenario毎に実行されるbackgroundとを分けて記述し、feature毎に実行するbackgroundのブロックのなかでFactoryGirlを 利用することで問題を回避することにしました。
実際には次のようなコードになります。

  # reply_spec.rb


  # feature毎に実行

  background(:all) do
    user = User.find_by(username: 'user')
    @message = create(:message_sent_to_all_user, user_id: user.id, subject: '受信テスト用回覧')
  end

  # example毎に実行

  background(:each) do
    signin('member1', 'changeme') # call sign in helper

  end

background(:each)のなかで、createを実行してもexample実行時に登録されていない状態でしたが、background(:all)の中で 実行したcreateではexampleの実行時ではデータが登録されており、期待通りのテストを実行することができました。
この理由をもう少し調べたいところですが今はテストの実装を優先している状況です。この辺のベストプラクティスがわかりましたらご紹介したいと思います。

注意点としては、example毎にデータが新たに登録されません(feature毎に登録される)ので、他のexampleに影響がないようにする点でしょうか。