こんにちは、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-scheduler : cronでキューに入れる。[参考]
- resque-lock-timeout : resqueをバックグラウンド実行中にロックをかける。[参考]
- resque-remote: resqueを別サーバーで実行
- resque-pause: 今ひとつわからない
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%を目指すのが現実的だと思います。
xUnit対RSpecという図式について、Railsでは当時からRSpecが支配的だったこともあり、弊社ではRSpecの使用が推奨されてきました。
RSpecでのノウハウ
以下パラパラとメモします。
- テスティングでよく使用されるのはAAAというパターンです。
- Arrange: 環境のセットアップ
- Act: テスト対象を実行
- Assert: 結果を検証
--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 decoratorやdraperなどのdecoratorパターンです。full_nameなどのメソッドを簡単に見通しよく追加できます。
decoratorパターンについては『肥大化したActiveRecordモデルをリファクタリングする7つの方法』も参考にしてください。