2014/01/12

railsでREST Web APIをつくってrspecでテストする。

railsでRESTfulなAPIを書いていて、テストはrspecを使うと決めていたのだが、対象がRESTful APIということで、 何か気にすることはあるのだろうか。テストというか、設計というか、これまで実践していたことと、 今回調べて新たに発見したことを合わせて、纏めておく。

テスト対象

テスト対象は、rails4で作ったRESTfulなAPI。全てのAPIはJSONフォーマットのみ対応。 つまり、htmlとかxml形式の応答はしない。今回試したのはRails4。

rspecとその他のgem

テスト環境にいくつかgemを追加して、テストの準備をする。 追加するのは、

の各gem。それぞれのgemの用途と設定をちょっと書きます。

rspec

テストを記述する。rspec、railsアプリのrspecでのテストの仕方は、

が参考になる。

railsアプリのテストでrspecを利用するための手順

-T付きでrails newする。

デフォルトのrailsアプリのテストは、Test::Unitが使われ、test用のディレクトリが生成される。-TオプションをつけるとTest::Unitが無効になり、testというディレクトリが生成されない(rspecのテストコードは、specディレクトリに入れる。)

$ rails new application_name -T

Gemfileの記述

Gemfileに以下を追加する。

group :development, :test do
  gem 'rspec-rails'
end

bundle installの実行

bundlerを使ってrpsecをインストールする。

$ bundle install

rspecの初期設定

rspecをrailsアプリに組み込むと、railsコマンドのgeneratorにrspec:installが追加される。 追加されているかは、rails g -hを実行すれば確認できる。

$ rails g -h
(略)
Rspec:
  rspec:controller
  rspec:helper
  rspec:install
  rspec:integration
  rspec:mailer
  rspec:model
  rspec:observer
  rspec:scaffold
  rspec:view
(略)
rspec:installを実行して、specディレクトリを作るとともに、spec_helper.rbを生成する。
$ rails g rspec:install
ここまでが、rails上でrspecを使うためのベーシックな手順。

simplecov

simplecovは、ruby用のコードカバレッジ計測ツールで、railsでも使える。 rspecとも簡単に連動できるし、生成されるレポートも見やすい。

Gemfileの記述

Gemfileに以下を追加する。

group :development, :test do
  gem 'simplecov'
end

bundle installの実行

bundlerを使ってsimplecovをインストールする。

$ bundle install

rspecとsimplecovの連携

spec/spec_helper.rbの先頭に次のコードを追加するだけで、rspecが動くたびにカバレッジを計測してくるようになる。 計測結果は、デフォルトではcoverageディレクトリにhtmlファイルとして出力される。

require 'simplecov'
SimpleCov.start 'rails'

guard-rspec

rspecを使ってテストを書いている場合、コードを直してspecを直して、rake specで全てのテストが問題なく通ることを確認して、というサイクルで開発していくことになる(このサイクル自体はrspecを使う場合に限らないけど。)。この時に修正したモジュールに対するspecの実行とか、全てのspecの実行とか、いちいちコマンドを叩いているのが面倒になる。そんなとき、guardを使うとspecの実行を自動化できるようになる。guardは、プロジェクト内のファイルの変更を監視して、変更が発生すると、ある動作を実行する、というもの。なので、rubyファイルやspecファイルをguardに監視させて、変更が発生したらテストを実行する、というように使う。


に詳しい説明があってとても参考になる。

Gemfileの記述

Gemfileに以下を追加する。

group :development, :test do
  gem 'guard-rspec'
end

bundle installの実行

bundlerを使ってgurad-rspecをインストールする。

$ bundle install

guardの設定

guardは、Guardfileの記述に従って動作する。Guardfileは、

$ bundle exec guard init rspec
で生成する。Guardfileに監視対象と、実行するspecの組み合わせを追加していくのだけど、これについては後述する。

guardの実行

guardを実行するには、

$ bundle exec guard
とする。すると、
13:36:21 - INFO - Guard is using NotifySend to send notifications.
13:36:21 - INFO - Guard is using TerminalTitle to send notifications.
13:36:21 - INFO - Guard::RSpec is running
13:36:21 - INFO - Guard is now watching at '監視対象パス'
[1] guard(main)> 
と表示され、監視が始まる。guardのプロンプトで、"all"(またはEnter)と打つと、全てのspecが実行される。

spring

guardを使ってrspecを動かす手間がかなり省けるようになると、次に気になるのがspecの実行時間。specの量が増えてくると、実行時間が長くなるし、そもそも起動時間も長くなって時間が勿体無く感じてくる。 springを使うと、railsの環境を先読みしてくれるようになるので、様々なプロセスの起動が速くなる。これをguardと組み合わせて使うことで、guardが実行するrspecを高速化することができる。

Gemfileの記述

Gemfileに以下を追加する。

group :development, :test do
  gem 'spring'
end

bundle installの実行

bundlerを使ってspringをインストールする。

$ bundle install

guardとspringの連携

guardとspringを合わせて使うには、Guardfileをちょっと修正するだけでよい。

guard :rspec, spring: true do # spring: true を追加する。
  :(略)
end

factory_girl

railsでrspecを使うときのテストデータに関して面倒なことがある。デフォルトだと、spec/fixturesディレクトリにテーブル(model)単位にymlファイルを作ってテスト時にロードして、っていうやり方になるけど、これだとモデル間のリレーションを考慮したテストデータづくりが非常に面倒くさいし、データが増えると管理できなくなる。この問題は、facotyr_girlを使うことで対応できる。factory_girlは、fixturesに定義していたymlをrubyで記述できるDSLを提供してくれている。そのため、データの定義やデータ間の関連がスッキリ書けるようになる。

などが参考になる。

Gemfileの記述

Gemfileに以下を追加する。

group :development, :test do
  gem 'factory_girl_rails'
end

bundle installの実行

bundlerを使ってfactory_girlをインストールする。

$ bundle install

JSONを返すRESTなAPI

ここまではどちらかというと、あまりJSONとかRESTとかとは直接関係しない、rspecを使ったテストを効率良く進めるための話題。 ここからはJSONを返すRESTなAPIのテストに関する話題。

にもろに影響を受けている。

APIのバージョニングを考慮する。

実際に経験したことでは、スマートフォンアプリをクライアントとするAPIを作っていて、アプリの機能を追加・変更して行く過程で、 API自体にバージョンの概念が必要になって、APIのリソースのURLにバージョン番号を埋めた、ってことがある。具体的には、例えばユーザ情報を取得するAPIが、

https://api.exapmle.com/users/id.json?id=xxxx
のように定義していて、ある時点から応答するデータの内容が大幅に変える必要がある、という場合。やったのは、
https://api.exapmle.com/v2/users/id.json?id=xxxx
という新しいURLを定義して、元々のAPIと並行して運用するということをした。このやり方(URLにバージョンを埋める)は、Twitter APIやFoursquare APIでも同じようなやり方をしているので、結構メジャーなソリューションなのかと思っている。railsでの実現方法は、

が参考になる。

APIのフォーマットをJSONに限定する。

railsのコントローラをgenerateコマンドで定義すると、デフォルトではhtmlを応答するアクションが生成されるけど、APIを作っている場合、htmlを応答することはまず無いし、一度JSONを返すと決めたら、後々xmlだとか他のフォーマットに対応させる、という変更が発生することも少ないのではないか、と思っている。なので、始めからJSONのみに限定したAPIにしてしまえば、設計もテストも楽になると考えた。

railsであるURLがデフォルトでJSONのみに対応するようにするには、

  • routes.rb
  • controller
  • view
を意識的にイジる必要がある。

config/routes.rb

例えば、ユーザ情報に関するAPIをrailsのresourcesを使いつつ、JSONに限定する場合は、

namespace :v1, defaults: { format: 'json' } do
  resources :users
end
としてあげると、通常必要となるURLの最後の".json"が不要となる。

controller

コントローラ側でJSONのみに対応させるには、

module V1
  class UsersController < ApplicationController
    respond_to :json

    def show
      @user = User.find(id: params[:id])
    end
  end
end
のように、先頭でrespond_toを読んであげれば個々のアクションでrespond_toを書く必要がなくなる。

view

JSON形式の応答も、独立したviewを作成することができる(知らなかった・・)。rails3.2から導入されたjbuilderというgemを使って、例えば上述のusers#showに対するviewは、app/views/v1/users/show.json.jbuilder というファイルを作ればよい。

jbuilderに関する解説もRailsCastの、


がわかりやすい。簡単にJSONのviewが作れるので感動的。

ここまでは、JSONを返すAPIの設計の話だったけど、次はテストの話。

テストは、Request Specに書く。

前述の「Rails API Testing Best Practices With RSpec」によると、APIのテストは、(Controller Specではなく、)Request Specに書くべき、ということらしい。 APIの呼び出しと応答(JSON)をテストする場合は、コントローラ単体というよりは、ルーティング、コントローラ、モデルなど各スタック間のIntegration Testに近いイメージで、rspecのRequest Specは、それがテストできるように設計されているから、ということっぽい。

JSON Helperをこしらえる。

JSONを返すアクションのテストを書くと、確かに

JSON.parse(response.body)
が頻発する。これをテストごとにいちいち書かずに、モジュールを作ってDRYに行こうぜ、と。
# spec/support/request_helpers.rb
module Requests
  module JsonHelpers
    def json
      @json ||= JSON.parse(response.body)
    end
  end
end
というモジュールを作っておいて、spec_helperで、
RSpec.configure do |config|

  config.include Requests::JsonHelpers, type: :request

end
としておけば、簡単にパースされたJSONオブジェクトにアクセスできるようになる。

まとめ

railsでREST Web APIをつくってrspecでテストする、という場合に、テスト関連ライブラリの準備や、APIの設計方法、テストのポイントをグワーっと書いてみました。中身が薄かったり、言葉足らずだったり、まとめきれてない感じもあります。更に精進して、改善していきたいと思います。

0 件のコメント: