TDD で作る RakuAPI ライブラリ
RakuAPI - 楽天市場 非公式ウェブサービス
という楽天の非公式 API のライブラリを作るのが流行みたいなので作ってみました。ただそれだけでは面白くないので、最近自分が TDD でライブラリ作るときの方法も軽くご紹介します。
まずはインターフェイスの構想
何はともあれ、どんなインターフェイスを定義して、どんな結果が返ってくるのかがイメージできないとライブラリは作りにくいです。というわけでざっくり最初に構想を練ります。
RakuAPI の場合は WebAPI がシンプルに使えて良い感じなので、構想を練るのに考え込む、というのはありませんでした。
そんなんで、RakuAPI.new でインスタンスを取得して、search メソッドで第一引数に検索文字列、第二引数はオプションでジャンルやプライスを渡せるように、結果は配列にStruct が格納されてる感じにしよう。と考えました。
テストを書く
構想があればそれに沿って書くだけですので超簡単。search メソッドで検索した結果が構造体で中身のメンバは云々、というのを書くだけでした。ちなみにいつもこんな感じのディレクトリ/ファイル構成でやってます。
RakuAPI
|-- lib
| `-- raku_api.rb
`-- test
`-- test_rakuapi.rb
じゃあ早速 test 書きますを。xUnit 互換の Test::Unit を使います。
# 上の階層の lib ディレクトリをライブラリパスに追加
$LOAD_PATH << File.dirname(__FILE__) + '/../lib'
require 'test/unit'
require 'raku_api'
class RakuAPITest < Test::Unit::TestCase
def setup
@raku_api = RakuAPI.new
end
def test_instance
assert_instance_of RakuAPI, @raku_api
end
def test_search
results = @raku_api.search 'Core 2 Duo', :genre => :pc
assert_instance_of Array, results
results.each do |result|
# 構造体かどうか
assert_kind_of Struct, result
# 構造体の指定したメンバの型チェック
assert_instance_of Fixnum, result.price
struct_methods_call result, %w(title tax url thumbnail_url shop_name shop_url) do |method|
assert_instance_of String, method
end
# url は http で始まってるかどうか
struct_methods_call result, %w(url thumbnail_url shop_url) do |method|
assert_match /^http/, method
end
end
end
def struct_methods_call(struct, methods)
methods.each do |method|
yield struct.send(method)
end
end
end
さて、search メソッドで検索して、結果がちゃんと取得できるかどうか、のテストがこれで書き終わりました。早速実行してみると…。
Loaded suite test_rakuapi
Started
EE
Finished in 0.006955 seconds.
1) Error:
test_instance(RakuAPITest):
NameError: uninitialized constant RakuAPITest::RakuAPI
test_rakuapi.rb:8:in `setup'
2) Error:
test_search(RakuAPITest):
NameError: uninitialized constant RakuAPITest::RakuAPI
test_rakuapi.rb:8:in `setup'
2 tests, 0 assertions, 0 failures, 2 errors
とまあ当たり前のように失敗します。で、テストを先に書くとここからが楽で、raku_api.rb の方で実装を書いたらテストを実行するだけ。で、最終的に全部テストが通るまで raku_api.rb を実装してとりあえず動く形にして、その後リファクタリングでわかりやすい or 速く動作するコードなど好きにアレンジします。
実際にかかった時間は、テスト書くのに10分、実装に15分ぐらいでテスト書くのに全体の 40%もかけてるじゃん!と思われるかもしれません。が、テストを書くことによって、実装時のチェックをいちいち print デバッグのような方法を行わなくてもすんで時間が削減できますし、なにより実装時のストレスが軽減されるので楽です。なのでライブラリのような物を作るときは、小物でもテストを先に書いておくとだいぶ楽になります。
で、最終形の raku_api.rb はこんな感じに。
#!/usr/bin/env ruby
begin
require 'rubygems'
require_gem 'scrapi'
rescue LoadError
require 'scrapi'
end
require 'uri'
class RakuAPI
RAKU_API_URI = 'http://rakuapi.ddo.jp/api'
class RakuScraper < Scraper::Base
def self.decamelize(str)
str.gsub(/(^.+)([A-Z])/, '\1_\2').downcase
end
elements = %w(Title Price Tax Url ThumbnailUrl ShopName ShopUrl)
elements.each {|el| process el, decamelize(el) => :text }
def collect
self.price = price.to_i
end
result *elements.map {|el| decamelize(el) }
end
attr_accessor :options
def initialize(options = {})
@options = {
:parser => :html_parser
}.update options
end
def search(keyword, options = {})
uri = URI.parse(RAKU_API_URI)
uri.query = queryize options.update(:keyword => keyword)
Scraper.define do
process 'Result', 'results[]' => RakuScraper
result :results
end.scrape(uri, self.options)
end
private
def queryize(hash)
hash.map {|i| i.map {|j| URI.escape j.to_s }.join '=' }.join('&')
end
end
XML なんだからパースには rexml とかでいいじゃん、と思うかもしれませんが、 scrAPI を使うことで、わざわざ Struct 作らなくても、結果が自動で構造体にマッピングされてちょう楽、です。collect メソッドを定義することで、最終的な型変換なども思いのままに行えます。scrAPI つかいたいだけちゃうんかと云われたらそんな気もしますが気にしません。
で、テストも通ってすっきりですね。
Loaded suite test_rakuapi
Started
..
Finished in 0.213071 seconds.
2 tests, 112 assertions, 0 failures, 0 errors
このライブラリの使い方はこんな感じで。(といっても test まんまですけど)
require 'raku_api'
$KCODE = 'u'
raku_api = RakuAPI.new
results = raku_api.search 'Perfume', :genre => :cddvd, :row => 2
require 'pp'
pp results
結果
[#<struct
title="Perfume〜Complete Best〜",
price=2999,
tax="税込、送料込",
url="http://item.rakuten.co.jp/book/4061751/",
thumbnail_url=
"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/ogs_410606/4106060429.jpg?_ex=64x64",
shop_name="楽天ブックス",
shop_url="http://www.rakuten.co.jp/book/">,
#<struct
title="Perfume",
price=3058,
tax="税込、送料別",
url="http://item.rakuten.co.jp/book/3991671/",
thumbnail_url=
"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/ogs_410603/4106031362.jpg?_ex=64x64",
shop_name="楽天ブックス",
shop_url="http://www.rakuten.co.jp/book/">]
というわけで、些細なライブラリでも TDD だと楽だよー、というお話でした。ちなみに TDD には↓の本から入りました。
posted with amazlet on 06.09.27
ケント ベック Kent Beck 長瀬 嘉秀 テクノロジックアート
ピアソンエデュケーション (2003/09)
売り上げランキング: 126,549