最近、JSも交えた総合テストを実施するのにRails界隈で良く使われている(と思う)jonleighton/poltergeist。
poltergeistはWebブラウザ操作DSLであるcapybaraのドライバとして動作します。
実際はphantomJSがバックエンドとして動いています。
phantomJSとRubyがどうやってやり取りしてるかは気が向いたら書くとして、
今回はpoltergeistを使って行う簡単なパフォーマンステストについて書きます。
ちなみに複数のクライアントから同時にアクセスするタイプのものではなく、
一つのクライアントでアクセスした時のレスポンスが来るまでの時間を測る感じです。
ラッシュかけたい場合には使えないので他の方法を考えましょう。
RSpecとcapybaraでfeature spec、つまり総合的なシナリオテストを書く時は以下のようになります。
feature "ユーザーを検索して一覧できる", js: true do
scenario "ユーザー検索ページを開き、ユーザーを検索して一覧する" do
visit users_path
find("#search_user_field").set("username")
click_button('Search')
expect(page).to have_selector(".user")
end
end
ページ開いてフォームに値入れて検索ボタンを押して結果確認、って感じです。
実際は、こんな簡単では無いですが…。
で、この検索結果のレスポンスが平均200ms以下で帰ってきて欲しいとします。
ここで、rack/rack-contribを利用します。
rack-contribにはRack::Runtime
というミドルウェアが含まれてます。
これはRack::Runtime
以下の処理に入って戻ってくるまでの処理時間をX-Runtime
というキーでレスポンスヘッダに追加してくれるミドルウェアです。
これを利用することで、実際にコントローラーがリクエストを受け取って、レンダリングして処理を返すまでの時間が簡単に測れます。
後はこれをrspec側で取得して集計できればオッケーです。
レスポンスヘッダの情報を取得する時にpoltergeistで使える機能は二つあります。
それがresponse_headers
とnetwork_traffic
です。
response_headers
は、最後にvisitで移動した際のレスポンスヘッダをハッシュの配列として返してくれます。
単純なハッシュじゃないので微妙にアクセスしづらいですが。
network_traffic
は、今までに発生したアクセス全てのリクエストとそれに対応する結果の記録を返してくれます。
これはブラウザ内のJSの動作で発生したAjaxのリクエストや画像の取得等も含みます。
これは地味に結構便利な機能です。
この例だとresponse_headers
で取得するだけで充分ですが、もしAjaxでフォームの送信が行われるような場合はnetwork_traffic
を使うと良いでしょう。
ちなみにnetwork_traffic
はclear_network_traffic
で消去できます。
これでヘッダの情報が取れるのでX-Runtime
の情報を集めて集計しましょう。
feature "ユーザーを検索して一覧できる", js: true do
scenario "ユーザーを検索して結果が返るまでの平均時間が200ms以下であること" do
response_times = collect_response_time(20, "POST", %r(/users/search$)) do
visit users_path
find("#search_user_field").set("username")
click_button('Search')
end
expect(response_times.sum / 20).to be <= 0.2 # Enumerable#sumはActiveSupport
end
end
def collect_response_time(n, http_method, url_pattern, &block)
response_times = []
n.times do
page.driver.clear_network_traffic # 前ループの履歴を消す
block.call
response_times << page.driver.network_traffic.find {|t|
t.method == http_method && t.url =~ url_pattern # アクセス対象を特定する
}.response_parts.first.headers.find {|h|
h["name"] == "X-Runtime"
}["value"],to_f # nameがX-Runtimeのヘッダのvalueを取得する
end
response_times
end
並列化とか考えてないのでそれなりに時間かかりますが、メタデータで上手く分けておいてJenkinsに日次で流すなどしてもらうのが良いかと思います。
そこまで正確ではないし1リクエストごとのレスポンスですが、メイン機能や重い計算が走る処理などに対して、手慣れたRSpecでざっくりとレスポンスが計測出来ると結構便利です。