はじめに
Railsのアプリの1つの機能としていくつかのサイトを巡回するクローラーを開発しており、
- HTTPクライアントの機能を利用してサイトにアクセスする機能
- (全部ではないけど)HTMLの要素をスクレイピングする機能
あたりは共通化できそうなのでそこだけモジュール化しようと思って実装を進めていきたのですが、肝心のモジュールに対するRSpecの書き方を知らなかったので、調べたことをまとめておきました
app/workers配下にクローラーを配置して利用することを想定してディレクトリ構成はこのようになります
app/workers
├── core_worker.rb
├── crawler
│ ├── searchable.rb
│ ├── site_a.rb
│ ├── site_b.rb
│ └── site_c.rb
ちなみに想定するクローラーの処理としてはこんな感じをイメージしてます
- 定期的ににcore_worker.rbが実行される
- 1日1回とか
- core_workerがsite_a.rb、site_b.rb、site_c.rbのそれぞれのクラスの初期化&クローリングを実行
- クローリングする時に、site_a.rb、site_b.rb、site_c.rbそれぞれで共通利用する処理をsearchable.rbにまとめる
手元の開発環境
- Mac OS X 10.8.5
- Ruby 2.2.2
- rbenv利用してインストール
- Rails 4.2
- rspec 3.3.0
モジュールに対するRSpecはいくつかアプローチがありそう
最初はRails4のconcernsなmoduleのテストをrspecで書く方法の情報にいきついたのですが、その後StackOverflowのTesting modules in rspecを見てたら参考になる書き方が紹介されてました。
ダミーとなるクラスでそのモジュールをincludeするようなやり方などいくつか紹介されてるのですが、個人的には
describe SomeModule, type: :helper do
# 省略
end
というhelper typeを利用した書き方が理解しやすく、Helper specの情報と合わせて読んだらイメージした形で書けたのでこの方針でいくことにしました
実際にどういう形になったのか?
まず、RSpecの方ですが、spec/workers/crawler/searchable_spec.rbというファイルを作って以下のように書きました。
require 'rest-client'
require 'nokogiri'
describe Crawler::Searchable, type: :helper do
describe '#page_source' do
let(:valid_url) { 'http://qiita.com' }
let(:invalid_url) { 'http://test' }
context '存在するサイトのURLを指定した場合' do
it 'スクレイピングの結果が得られる' do
doc = helper.page_source(valid_url)
expect(doc.present?).to eq true
end
end
context '存在しないサイトのURLを指定した場合' do
it 'falseが返る' do
doc = helper.page_source(invalid_url)
expect(doc.present?).to eq false
end
end
end
end
上記のRSpecにパスするようにapp/workers/crawler/searchable.rbをこのように実装しました。
module Crawler
module Searchable
def page_source(url)
return false if url.empty?
begin
return RestClient.get(url)
rescue => e
return false
end
end
end
end
RSpecの実行結果も貼っておきます
./bin/rspec spec/workers/crawler/searchable_spec.rb
Crawler::Searchable
#page_source
存在するサイトのURLを指定した場合
スクレイピングの結果が得られる
存在しないサイトのURLを指定した場合
falseが返る
Finished in 0.2259 seconds (files took 4.12 seconds to load)
2 examples, 0 failures
※本文の内容と直接関連がないですが、RSpecの実行結果がドキュメント形式で表示されるように.rspecを以下のようにしてます。
ここの記述内容がほかの人に理解しやすい形になっているかどうか気にすることで、describe/contextの使い分けが妥当かどうか気づけるからです。
--color
--require spec_helper
--format documentation