Rails勉強会まとめ — 非同期処理/RSpecなど

こんにちは、hachi8833です。

今回は棚卸しとして、弊社CTOのbabaさんによるRails勉強会スライドから引用して記事にします。
勉強会自体はRails 3.x時代のものなので既出が多くなっていますが、棚卸しも兼ねて今のうちに記事にいたしました。

非同期処理

言うまでもなく、リクエストからレスポンスまでの時間が長くなるほどユーザー・エクスペリエンスの質が低下します。これを改善するためにさまざまな工夫が必要になるわけですが、その中の一つとして、時間のかかる処理をバックグラウンドで実施しておくというのがあります。

cron

最も素朴な方法はやはりunix標準のcronを使って定時に出力データを準備することでしょう。この場合、起動用スクリプト(rakeなど)やジョブ管理は自分で作成する必要があります。

delayed_job

delayed_job gemを使用すると、ジョブをActive Recordで管理できます。
その便利さについては各方面で語られているので省略しますが、以下の点が少々残念であるという指摘があります。

  • ジョブ完了時にキューから該当ジョブを単純に削除するため、完了したのか、Enqueueに失敗したのかわかりにくい
  • データをMarshallで渡すため、バージョンが変わったときにトラブルの元になることがある
  • Railsが動作していることが前提のため、その分重い

resque

3番手はresqueです。resqueはredis (C言語で書かれたKey-Valueストア) を使用してジョブキューを管理します。Redisとresqueがインストールされていれば、Railsなしでも実行可能です(ActiveRecordがないと使いにくいとは思います)。

resqueはJSONでデータを受け渡しするので互換性が高いのが特長です。

※Redisには少々癖があり、旧バージョンでは書き込みIO負荷が高い・メモリ不足の時に落ちる・expireしても次回読み込みまで消えないなどの問題があったようです(2012年時点の情報なのでその後状況が変わっている可能性あり)。

使い方は以下をどうぞ。

resqueはgemで拡張するのが習わしになっています。

resqueの注意点など

  • resqueはマスタープロセスからforkさせてからキューを実行するので、デプロイ時にはマスタープロセスの再起動が必要。
  • Workerクラスはモデルの内部クラスとして実装すると見通しがよくて便利です。
  • resqueの監視にはmonitが無難でおすすめです(godよりは安定しているみたい)。
  • Mysql::Error: MySQL server has gone awayエラーが発生する場合は、最初にActiveRecord::Base.verify_active_connections!を行ってみよう。database.ymlにreconnection:trueと書く手もあるようですが、長いトランザクションの途中でreconnectされると怖いかも。

RubyのWebアプリケーションフレームワークについて

sinatraといえばRubyによる軽量フレームワークとして今や定着しました (一時はRails3の登場で絶滅が危惧されたそうです)。プロジェクトの規模に応じて、RailsとSinatraを使い分け、場合によってはRackを直接駆動するというのがよい感じです。

ちなみに紹介記事を元に生Rackアプリを書いてみたら、えらく簡単でした。

class MyApp
  def call(env)
    [
      200,
      { 'Content-Type' => 'text/html' },
      ['<html><head></head><body><h1>hello</h1></body></html>']
    ]
  end
end

テスト駆動開発(TDD)について

TestFirstとTDDは同じものではありません。TestFirstは単にテストが先行しているだけの状態であり、TDDはテストが開発を牽引しているかどうかという点が違います。

テストのカバレッジですが、一般的に言って100%にこぎつけるのはやはり難しいと言えます。カバレッジにはC0/C1/C2などの段階がありますが、たとえばbegin/rescueブロックをどうチェックするか、fork先のプロセスまでチェックするかなど、追い求めるときりがありません。Coverageは90%を目指すのが現実的だと思います。

xUnitRSpecという図式について、Railsでは当時からRSpecが支配的だったこともあり、弊社ではRSpecの使用が推奨されてきました。

RSpecでのノウハウ

以下パラパラとメモします。

  • テスティングでよく使用されるのはAAAというパターンです。
    1. Arrange: 環境のセットアップ
    2. Act: テスト対象を実行
    3. Assert: 結果を検証
  • itでのテスティングの説明は極力シンプルにしましょう。ほとんどの場合1行以内に収まるはずです。
  • beforeをうまく使ってテスティングの下準備部分をまとめ、効率化するのがポイントです。デフォルトではbefore :eachなのでその場合:eachは省略できます。
  • itを生成するメタプログラミングもありですが、あまり複雑にするのはよくないようです。入力・出力のペアをまとめて検証するぐらいにしておきましょう。
  • JavaScriptのテストは一律行うと遅いので、必要な部分のみを実施するようにしましょう。最初にJSをオフにして全テストを実行し、最後にJSが必要な部分だけを実行すると効率的です。具体的には、decribeブロックごとにRackとWebKitを切り替えます。つまり、必要な場合にのみWebKitを使用してJavaScriptをテストし、通常は極力Rackを使用します。RackはWebKitより10倍速いので。
  • 毎回RSpecをフルで回していると時間がかかってしょうがないので、RSpec実行時に --fail-fast を指定すると、失敗が1つ発生した時点でテストが終了するので、うまく使えば時間を節約できます。
  • bundle exec rspec spec / --fail-fast
    

RSpecのマッチャー

RSpecも少し前に3.0が登場しましたが、以前のノウハウもまだまだ健在です。
RSpecのマッチャーで真っ先に覚えるべきはbe_hogehoge系のpredicate(述語)マッチャーでしょう。このマッチャーが機能するには、hogehoge?というメソッドが必要ですので、逆に言えばこのマッチャーが使えるようにコードを書くようにしましょう。ついでながら、このメソッド名もマッチャーがおかしくならないような名前にしておきたいものです。「is_flag?」みたいなメソッド名だとマッチャーが「be_is_flag?」みたいなみっともない名前になるので、そういう名前は使うものではないということがわかります。

他にビルトインのsatisfyマッチャーはブロックを引数に取り、ブロックがtrueかどうかをチェックします。

raise_errorマッチャーは例外が発生するかどうかを確認できるビルトインマッチャーです。例外のテストは意外と忘れがちです。

それから、カスタムマッチャーは遠慮せずにどんどん作りましょう。複雑なオブジェクトをeqで比較できるようにするとか。

UIテストについて

TestFirstが常に善とは限りません。UIを試行錯誤しながら開発するときにはTestFirstはほとんど意味がないのでとっとと諦めましょう。UIにびっしりテストを書いたところで、仕様が変わったときのダメージが大きいので、必要な分だけにしておきましょう。

UIのテストでは、むしろ「/usersだったらuserがたくさん表示されていることを確認する」など、瑣末でない本質的な部分を押さえるようにしてください。後、クライアントの指示で必ず含めなければならない定番の文言があるならそれをテストに含めるのも有効です。

テスト内容がCSSセレクタでデザインに依存するのはよい傾向ではありません。注意しましょう。

テストデータ

単体テストでは、テスト対象以外の関連オブジェクトのデータを作るかどうかが思案のしどころです。大きく分けて、モックでさくさく進めるか、ちゃんとオブジェクトを作成(Object#create)するかです。

  • モックは動作も高速で比較的作りやすいのですが、関連オブジェクトの仕様が変わっても検出できないという問題があります。
  • Object#createは面倒な代わりに、関連オブジェクトの仕様が変わった場合に検出することができます。

どちらを選ぶにしても、テストをどのように分離し、網羅するかはしっかり考えておく必要があります。単体テストはその対象だけに集中すべきと考えるなら、結合テストで関連オブジェクトとの関係をカバーする必要がある、というふうに。

ところで、テストで時間を扱うにはtimecop gemが便利です。

テスト環境・ドキュメント

CI(継続的インテグレーション)サービスにはtravis CIを始め多くのサービスが出現していますので、積極的に使いましょう。

rspec --format documentation -o 出力ファイル名でRSpecのログを残せます。rspec --format html -o 出力ファイル名とすればHTML形式で出力できます。

自動生成ドキュメントは何かと使いづらいので、必要なドキュメントがあれば自分で書きましょう。

  • 主要エンティティER図
  • 主要エンティティ状態遷移図
  • などなど

active decorator

ヘルパーファイルの命名はビューに依存していて、数が増えてくるとapp/helperの下のどれに追加したらよいか迷ってしまいます。メソッドを追加したいのはモデルであることが多いのですが、モデルが肥大化するのは避けたいものです。

そんなときにはactive decoratordraperなどのdecoratorパターンです。full_nameなどのメソッドを簡単に見通しよく追加できます。

decoratorパターンについては『肥大化したActiveRecordモデルをリファクタリングする7つの方法』も参考にしてください。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ