Ruby on Rails: The RSpec Book 読破記録: 14章から

■14章ではRSpec::Mocksについて説明されます。
あるオブジェクトがそれ単体で動作すればこれを使う必要はありません。通常はなにかほかのオブジェクトとのコラボレーションによって機能を果たします。しかし、それらのチェーンを追っていくと結局のところすべてのオブジェクトを作ってからようやくひとつのテストができあがるという事態になりかねません。事実私はRSpecを勉強しだした時に、Ruby on RailsのScaffoldを使ってかたっぱしからオブジェクトを作ってしましました。。オブジェクトをとりあえず作ること自体は合っていました。方法が間違っていて、scaffoldを使うべきではなく、RSpec::Mocksライブラリを使うべきだったのです。
●テストダブル←モック・スタブ・フェイク・スパイともよばれる。テストダブルといったときに、それが意味するものがいろいろであるため、混乱する。double,stub,mockメソッドは動作は同じで意味が違う。違いは後ほど説明するとのこと。
● メソッドスタブ ←とりあえず期待するレスポンスを返すようにするメソッド。
●メッセージエクスペクテーション ←一度も呼び出されないとエラーになる。
●メッセージエクスペクテーションは実際にアサーションであるが、アサーションされるのはコードが最後まで通過した後なので、Given Then Whenがわかりづらくなることが指摘され、それを解決するTest Spyパターンが存在することが紹介されています。ただ、私にはそれほどわかりづらいとは感じられず、むしろnot-a-mockやRRを使うほうが難解に思えました。たぶんこれらの説明が少ししかないからなので、後ほど調べてみようと思います。

●テストダブルを使うことによってデータベースから切り離してテストを行うことができると説明されています。Railsの場合はモデル、つまりActiveRecord=データベースのようなところがあるので、モデルのテストではテストダブルはそれほどつかわないだろうなと想像できます。逆にコントローラーではモデルのロジック(メソッドの内容)と切り離してテストがしたいので、これらを使わない手はないですね。

●ショートカット
customer = double(‘customer’)
customer.stub(:name).and_return(‘Bryan’)

customer = double(‘customer’, :name => ‘Bryan’)

複数でも指定できます。
customer = double(‘customer’)
customer.stub(:name).and_return(‘Bryan’)
customer.stub(:open_source_projects).and_return([Webrat’,’Rack::Test’])

costomer = double(‘customer’, :name => ‘Bryan’, :open_source_projects=> [Webrat’,’Rack::Test’])
※ここまで長いと逆にややこしいかも

●スタブチェーン
※ある執筆者が書いた新しい書物を取得する(RoR上のコード)
Article.recent.published.authored_by(params[:author_id])
↓スタブチェーンで実装すると
article = double()
Article.stub_chain(:recent, :published, :authored_by).and_return(article)
とする。オブジェクトが入れ子になっている場合に奥のほうのメソッドをスタブ化したい場合に便利。

●カウント(メッセージエクスペクテーションが呼び出される回数を指定する)
hoge.should_receive(:fuge).exactly(1).times #1回hoge.fuge()が呼ばれる
hoge.should_receive(:fuge).exactly(5).times #5回hoge.fuge()が呼ばれる
hoge.should_receive(:fuge).at_most(5).times #5回までhoge.fuge()が呼ばれる
hoge.should_receive(:fuge).at_least(5).times #5回以上hoge.fuge()が呼ばれる

1回の場合と、2回の場合はショートカットがある。
hoge.should_receive(:fuge).once #1回hoge.fuge()が呼ばれる
hoge.should_receive(:fuge).twice #2回hoge.fuge()が呼ばれる

●一回も呼ばれないことを期待する
hoge.should_not_receive(:fuge)
↓そのほか、あんまりつかわなそうな書き方
hoge.should_receive(:fuge).never
hoge.should_receive(:fuge).exactly(0).times

●この引数で呼ばれるはずだと期待する
hoge.should_receive(:fuge).with(50)
hoge.should_receive(:fuge).with(50, something)

●引数がこのインスタンスであることを期待する。
hoge.should_receive(:fuge).with(instance_of(Fixnum))
※通常はこのやりかたはオススメしないとのこと。そのサンプルの意図をはっきりさせるためであればつかってくれと。。しかし、どうしてオススメしないのかはかかれていません。たぶんテストで使われるデータには具体的な数値や文字などが使われる方が好ましく、そうであれば型をチェックする理由はないからなのかなぁと想像しています。

●なんらかの引数があることを期待する
hoge.should_receive(:fuge).with(anything())
hoge.should_receive(:fuge).with(anything(), 50)

●withがなければどんな引数でも受け取るけど、あえてそれを明示したい場合。
hoge.should_receive(:fuge).with(any_args())

●引数なしで呼ばれることを期待する
hoge.should_receive(:fuge).with(no_args())

●引数がハッシュのときに中身まで期待する
hoge.should_receive(:fuge).with(hash_including(‘A’=>1,’B’=>2))

●逆に引数がハッシュのときに中身にふくめてはいけないことを期待する
hoge.should_receive(:fuge).with(hash_not_including(‘A’=>1,’B’=>2))

●正規表現も使える
hoge.should_receive(:fuge).with(/.*User/)

●カスタムマッチャも使える※ある数よりも大きいことを期待するカスタムメソッドの例
hoge.should_receive(:fuge).with(custom_method(3))
#methodはクラスを返す
def custom_method
CustomClass.new(expected)
end
#classは「==(actual)」というメソッドを定義する。←そうすると後で呼ばれるらしい。
#description()メソッドを定義をしておけばエラーが起きたときのメッセージを指定できる
#initialize()メソッドを定義してクラス変数を保存すればより使いすくなる。
class CustomClass
def initialize(expected)
@expected = expected
end
def description
“#{@expected} より大きい”
end
def ==(actual)
actual > @expected
end
end

●連続して値を返すことを期待する ※本の中ではネットワークの接続を3回まで試すことを題材に説明しています。
hoge.should_receive(:fuge).and_return(nil,nil,nil)#hoge.fuge()はnilを3回返す
↓こうも書ける
hoge.should_receive(:fuge).exactly(3).times.and_return(nil)

●エラーが発生することを期待する
hoge.should_receive(:fuge).and_raise

●エラーの型を指定できる
hoge.should_receive(:fuge).and_raise(InsufficientFunds)

●エラーの型を指定したいが、そのエラーに引数がある場合は一度インスタンス化してそれを渡す。
instance = InsufficientFunds.new(:reason => :on_hold)
hoge.should_receive(:fuge).and_raise(instance)

●エラーではなくてシンボルを渡したいときはand_throw
hoge.should_receive(:fuge).and_throw(:insufficient_funds)

●正しい順序で呼ばれることを期待する
hoge.should_receive(:first_method).ordered
hoge.should_receive(:second_method).ordered
※逆に言うとorderedを付けないと順序は無視。
※ひとつのオブジェクトのみに有効。複数のオブジェクトに指定しても順序無視

●スタブは上書き可能
before do
@logger = double(‘log’)
@logger.stub(:log).and_return(nil)
end
it ‘test’ do
@logger.should_receive(:log).with(‘something text’)
end
※この使えばデフォルト値を設定できるのでコードを簡潔にできるときがある。 多用すると複雑になるかも。

●テストダブルの使いどころ
・まだ作ってないオブジェクトに
・ネットワーク、サーバー、ファイルシステム、などの外部システムに
・動作に時間がかかるオブジェクトに
・そのほか不確定になりすいオブジェクト

● モック作ると役割が見えてくることがある。状態ではなくやり取りに合わせる。と説明されています。
確かにモックを使えば、あるオブジェクトがそのモックにほしいメソッドを書きたくなるので、役割・メソッド(インターフェース)が見えてくる気がします。 あるオブジェクトに別のオブジェクトに必要なメソッドがわかったときに、そのオブジェクトに直接ロジックを書き始めるよりも、とりあえずメソッドだけ定義しておいて、モックを使ったほうがひとつのオブジェクトに集中できるし、視点が「使う側」なので、相手を「なんらかの仕事をする者」として見れますよね。

●リスクとトレードオフ:やりすぎには注意しましょう!
・1つのサンプルでモックの数が増えたら、結びつきがつよくなっているかも
・テストダブルを入れ子にし始めたら、それらを分離できる証拠
・「とりあえず」で指定したモックは偽のOKを出す危険があるので、高レベルの自動テストを導入しよう

●ここで、他のテストダブルフレームワークの使い方の説明がされます。
RSpec::configure do |config|
config.mock_with <フレームワークID>
end
フレームワークIDには:rspec,:mocha,:flexmoch,:rrのいずれかがある。デフォルトは:rspec

●その他を使うにはカスタムアダプタを作成します。とあり、MockFrameworkAdapterの内容について説明されています。
RSpec::configure do |config|
config.mock_with CustomMockAdapter
end
※そのファイルをどこにおけばいいかについては触れられていないのですが、どうやらコレのことらしく、私の環境の場合は以下のディレクトリでした。
~/.rvm/gems/ruby-1.9.2-p290/gems/rspec-core-2.8.0/lib/rspec/core/mocking

 

いやー。14章も長かった。
正直な話、14章を一度読んでも何にもわかりませんでした。例を交えてわかりやすく説明してくれているのですが、サンプルコードのキモがドコなのかわからず、本文がどこを指しているのかわからなかったからです。実装経験の多い方であればおそらくすぐにわかるのだと思いますが、Rubyでさえ初心者の私には無理でした。

くじけそうになりましたが、避けて通れないと思い直し、わからないながらもこの先の章をだーっと呼んでいくと、19章からRailsに対しての記述があります。そこを読み進めていくと、おー、わかるわかる。

途中で本を読むのをやめて、今進めているRailsのプロジェクトにわからないながらRSpecやCucumberを実際に使ってみました。

その後で14章に戻ってくると、前はわからなかった概念がよくわかるようになっていました。

ここまで読んできてこの本は、実践に則してよくまとめられている一方で、文章がなぜかわかりづらいと感じます。内容が抽象的な概念を説明してたりするので、主語述語やセンテンスの関係性があいまいな日本語にそのまま変換すると、わかりづらくなるんですかね。

とはいえRSpecを題材にした日本語の書籍といえば、現段階ではほぼこの本だけですから、ありがたく読ませていただきます。15章につづく。

 

 

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です