Ruby on Rails でのモックとスタブの作成

RSpec、Mocha、そして Flex Mock に関する Ruby のテスト戦略

私が前回 RSpec に関する記事を公開した後、何人かの読者が、RSpec ではモック・オブジェクトはどんな風になるのかと質問してきました。正直に言うと、前回はモック作成用フレームワークの話題を意図的に避けたのです。

RSpec チームでは、モック作成用フレームワークの構築と保守のための投資を続けるべきか、あるいは既存のフレームワークの 1 つを採用すべきかについて、内部で少し議論がありました。この記事ではスタブとモックを作成するための基本を、順を追って説明します。そしてスタブの作成とモックの作成の両方の例を、一般的な 3 つの Ruby フレームワークを使って説明します。

問題

テストを自動化するための、ほとんどすべての戦略は、いくつかの基本的な概念に基づいています。テストは下記の条件を満足する必要があります。

反復可能なこと。テストを自動化する場合には、テスト結果を検証できるように、テスト・ケースは毎回同じ結果を返す必要があります。

実行時間が短いこと。テスト・ケースの実行に時間がかかりすぎるようだと、テスト・ケースを実行しなくなるのでテスト・ケースが役に立ちません。

単純なこと。テストの作成が難しすぎると、テストを作成しなくなります。

こうした条件をテストが満足する必要があることから、優れたプログラマーはアプリケーションの実行に時間がかかる部分や、予測できない部分、あるいは複雑な部分を置き換えるための方法を探します。長年の経験の積み重ねによって、この業界はシステムのさまざまな部分を置き換えるための方法として、少なくとも 2 つの重要な手法を確立しています。これは建築業者が建設中の構造物を支えるために一時的な支持物を構築するのと似ています。

モックはスタブではありません

約 4 年前、ThoughtWorks の Martin Fowler が、Mocks Aren't Stubs (モックはスタブではありません) という有名なブログ・ポストを公開しました (「参考文献」にリンクがあります)。それから 4 年経ちましたが、大部分の開発者は相変わらずこの 2 つの概念を混同しています。(話がそれますが、もしかすると私達は、これらの概念の表現としては不適切な言葉を選んでしまったのかもしれません。しかし今や、これらの言葉を使わざるをえません。) では、「たとえ話」を使って 2 つの概念を明確にしてみましょう。

毎年クリスマスには、私と妻はクリスマス・ツリーを取り出し、それに飾り付けをします。ツリーの飾り付けは楽しい思い出となる作業のはずですが、私にとっては悪夢です。私は電球と格闘しなければなりません。毎年、電球がいくつか切れてしまい、その電球がついた電線の他の電球まで点灯しなくなってしまいます。実際には、作業は簡単です。切れている電球を探せばよいのです。点灯しない電球を含んだ電線を、1 つずつ電球をテストしながらたどっていきます。各電球のテストは、少なくとも 2 つの方法で行うことができます。この 2 つの方法が、スタブを作成する方法とモックを作成する方法に似ているのです。

スタブの作成

私は、電線に付いている電球を、点灯する電球まで含めて 1 つずつ取り外して交換する必要はないことを学習しました。複数の電球が切れている可能性がありますが、1 つずつ電球を交換する代わりに、1 つずつ電球をテストするのです。テストには 2 つの方法が有効です。最初の方法は、電球を 1 つ外し、ちょっとした電池駆動のライトにつけてみます。もし点灯すれば、その電球には問題はなく、次の電球に進むことができます。もし点灯しなければ、その電球をハンマーでたたき潰し、点灯する電球と交換します。私が何をしているのか、理解できると思います。私は複雑に電球が接続された電線を、ずっと単純な装置で置き換えているのです。次に、電球に電気を通してチェックし、結果を観察し、そしてそれによってテストの合否を判断します。

さて、この電球を、クラスまたはコンポーネントと考えてみてください。このオブジェクトを使用するクライアント・コードは、電球付きの電線です。ソケットはインターフェースです。スタブを作成する場合はインターフェースを保持しますが、テスト対象のオブジェクトに対するクライアント・コードの一部あるいは全部を置き換えます。

モックの作成

モック・オブジェクトも似ていますが、もう少し複雑です。もし私が電球をテストする装置を持っていなかったら、テスター (回路計) と呼ばれるツールを気軽に使うことができます。テスターは、電球に電気が流れるかどうかを計測します。このプロセスは似ています。1 つずつ電球を外し、電球にテスターの 2 本の電極を当ててテストします。テスターはその回路に少量の電気を供給し、電気が流れるかどうかを計測します。少し頭を働かせてみれば、この計測で実際に行っていることは、電球がその回路をどう利用しているかをテスターで測定している、ということがわかります。つまりモック・オブジェクトは一種のスタブです。モック・オブジェクトは、テスト対象のオブジェクトを使用するクライアント・コードを置き換えます。しかしモック・オブジェクトはそれ以上のことを行い、テスト対象のオブジェクトがクライアント・コードを実際にどう使うかを測定するのです。

データベース・スタブは、クエリーへの応答として偽のレコードを返します。モックも同じことをしますが、モック・データベース・オブジェクトは、そのクライアント・コードがある種の一連のクエリーを呼び出し、最終的には close を呼び出して接続を閉じたことまで確認します。インターフェースの使い方をテストする場合にはモックを、インターフェースの使い方をまったく気にしない場合にはスタブを使う必要があります。

欠点

スタブを作成する方法とモックを作成する方法は強力な手法であり、テスト・ケースに要する時間が短くなり、コードを分離し、テストを単純化し、そして (一部の人は信じていますが) 世界の飢餓を解決します。しかし、そこには必ず大きな「ただし書き」があることに注意してください。モックとスタブを作成する際には、実際のコードを置き換えすぎないように注意する必要があります。私は、最終的に実際のコードに似せて何でもスタブ化してしまった顧客を数多く見てきています。ある時点で、やっかいなデータベース・コードをアプリケーションの他の部分と合わせてテストする必要があることを忘れないでください。皆さんにも注意しておきます。

スタブ作成の基本

どのようなフレームワークに対してどのようなスタブを作成する場合も、基本的な方法は同じです。下記の 2 つを行う必要があります。

テスト・ケースの中で、実際のシステムの一部をスタブで置き換える。 対象とする、実際の動作をスタブの中に複製する。

オブジェクト全体を置き換える場合もあれば、あるいは単に 1 つのメソッドの結果を変更するだけの場合もあります。オブジェクト全体を置き換える場合は簡単で、新しいオブジェクトを作成してテスト・ケースの中で置き換えるだけです。一部の言語では、メソッドの結果を変更するのは難しいかもしれません。しかし Ruby のような動的言語を使えば、メソッドを再定義するだけなのでスタブの作成は容易です。やれやれ、この理論は少し抽象的になりすぎました。今度は、こうした概念を現実にします。

文書を印刷するユーザー・インターフェースがあるとしましょう。印刷の失敗を処理するコードをテストしたいとします。print メソッドが呼び出されるかどうかは気にしません。コードが実際に print を呼び出すかどうか、失敗した print をコードが適切に処理するかどうかのみを検証したいとします。この場合の擬似コードの文がどんな風になるかを考えてみてください。

文書オブジェクトのインスタンスに対して print メソッドのスタブを作成し、偽を返す。

この文は 2 つの部分に分割することができます。最初の部分はスタブを作成したい対象を特定します。2 番目の部分はスタブが何をすべきかを定義します。では、このスタブを 3 つのフレームワークを使って作成しましょう。

Mocha/Stubba

現在のところ、モックとスタブを作成するための最高のフレームワークの 1 つは Mocha です。Mocha は gems を使って簡単にインストールすることができます。

リスト 1. Mocha をインストールする

batate$ gem install mocha Bulk updating Gem source index for: http://gems.rubyforge.org Successfully installed mocha-0.5.5 Installing ri documentation for mocha-0.5.5... Installing RDoc documentation for mocha-0.5.5...

Mocha のデモンストレーションを行うためにはアプリケーションが必要です。リスト 2 のアプリケーションは、 Document というモデルと View というビューで構成されています。このユーザー・インターフェースは印刷を Document に委任します。

リスト 2. document.rb によるアプリケーション

class Document def print # doesn't matter -- we are stubbing it out end end class View attr :document def initialize(document) @document = document end def print() if document.print puts "Excellent!" true else puts "Bummer." false end end end

Document クラスを結局スタブにしてしまうので、このクラスにはシェルを作成する以上の手間はかけません。実際、スタブ作成用フレームワークの大きな利点の 1 つは、少しずつ追加しながらテスト駆動開発を行えることです。この場合では、ユーザー・インターフェースを作成するために完全な Document モデルを持つ必要はありません。

リスト 3 はテスト・ケースを示しています。

リスト 3. Mocha を使ってテストする

require 'test/unit' require 'rubygems' require 'mocha' require 'document' class ViewTest < Test::Unit::TestCase def test_should_return_false_for_failed_print document = stub("my document") document.stubs(:print).returns(false) ui = View.new(document) assert_equal false, ui.print end end

この動作は理解できると思います。 stub("my document") という命令を使って、単純な、名前付きのスタブ・オブジェクトを作成します。次に、 document.stubs(:print).returns(false) というコード行によって、印刷動作をこのスタブ内で定義しています。注意深く見ると、このコード行は先ほどの擬似コードと驚くほど似ています。

文書オブジェクトのインスタンスに対して print メソッドのスタブを作成し、偽を返す。

このテスト・メソッドの最初の 2 行は、下記のように 1 行に単純化することができます。

リスト 4. テストを単純化する

Document.any_instance.stubs(:print).returns(false)

リスト 4 のバージョンは、 print 用のスタブを Document クラスの任意のインスタンスに置き換えています。Ruby フレームワークの中でスタブを作成することによる便利な点の 1 つは、構文は少し変更が必要であっても、基礎となる概念と構造はまったく同じに維持されることです。

Flex Mock でスタブを作成する

Flex Mock も Mocha と同じように、よく使われるモック作成用フレームワークです。rake など日常的に使用する Ruby ツールの作成者として有名な Jim Weirich が、Ruby のテストでの基本的なモックとスタブの作成を行うために Flex Mock を作成しました。人気と実用性の点で、Mocha と Flex Mock は非常に似ています。Flex Mock のインストールは、gems (これも Jim Weirich のプロジェクトです) を使えば簡単です。

リスト 5. Flex Mock をインストールする

gem install flexmock

flexmock メソッドを使ってスタブを作成することができます。そしてスタブを作成したいメソッドの名前をキーとし、値を戻りとするハッシュを与えます。スタブの作成はリスト 6 のように行います。

リスト 6. Flex Mock を使ってスタブを作成する

require 'rubygems' require 'test/unit' require 'flexmock/test_unit' require 'document' class ViewTest < Test::Unit::TestCase def test_should_return_false_for_failed_print document = flexmock(:print => false) ui = View.new(document) assert_equal false, ui.print end end

構文は少し異なりますが、概念はまったく同じです。ここでは document を、 print に対して false を返す単純化したスタブで置き換えています。

RSpec

RSpec では Mocha と Flex Mock の両方が使えることを忘れないでください。とは言うものの、RSpec でスタブを作成するための構文は Mocha と似ています。リスト 7 はその方法を示しています。

リスト 7. RSpec でスタブを作成する

document.stub!(:print).and_return(false)

この構文は、Mocha の構文と驚くほど似ています。RSpec に関して判断が必要となる主なポイントは、間もなく非推奨となる可能性のある API を使うか、あるいは別のテスト・フレームワークを RSpec に追加するかどうか、という点です。現状では、RSpec の良いところの 1 つは、RSpec がテスト用のワンストップ・ショップであることで、RSpec は重要な懸念事項をすべて処理してくれます。しかし Mocha と RSpec の構文を並べて比較してみると、RSpec テスト・ケースの中で Mocha を使っても失うものは少ないことに異論はないでしょう。

モック作成の基本

ここまでで学んだように、モック・オブジェクトの作成は、スタブの作成とよく似ています。違いは、スタブは受動的であるということです。スタブは、スタブの作成対象のメソッドに対して呼び出しを行う実在のソリューションを単にシミュレーションするにすぎません。一方モックは能動的であり、モック・オブジェクトを使って行うその方法を実際にテストします。想定の動作と一致する方法でモックを使わないと、テストは失敗します。下記はモックを使う場合の基本的なステップです。

テスト・ケースの中の、実際のシステムの一部をスタブで置き換える。 対象とする、実際の動作をスタブ内に複製する。 想定の動作を定義する。 テストの後、実際に起きたことを想定の動作と比較する。

テスターとクリスマス電球の話を思い出してください。私は、テスト対象のオブジェクトを表す電球を外しました。そして実際のアプリケーションを表す、たくさんの電球がついた電線を、テスト対象のオブジェクトを表すテスターで置き換えました。私は 2 つの暗黙のステップを定義する必要があります。まずはテスターを使用しました。その理由は、電球が切れていないかどうかテスターの針が示してくれることを期待したからです。そして私はテスト結果としてテスターを見ました。これらの暗黙のステップが、ステップ 3 とステップ 4 を表しています。実際には、通常はテスト・ケースが完了すると、テスト・フレームワークがステップ 4 を処理します。この文書印刷アプリケーション用のモック・オブジェクトを記述する擬似コードは次のようになります。

document オブジェクトに対して、(偽を返す) print メソッドへの呼び出しを想定する。

Mocha、Flex Mock、そして RSpec を使ったモックの作成

ある概念を 1 つの文で表現できる場合には、その概念を 1 行か 2 行の Ruby コードで実装できることが多いものです。リスト 8 は、リスト 4 のスタブをモックに変更する、信じられないほど単純な Mocha コードを示しています。

リスト 8. テストを単純化する

Document.any_instance.expects(:print).once.returns(false)

ここでは私の想定を反映させるためにメソッド stubs を expects に変更しました。またクライアント・コードが print を何度呼び出す必要があるかを示すために once メソッドを追加しました。このテスト・ケースは、テスト対象のアプリケーションが Document クラスの任意のインスタンスに対して print メソッドを正確に 1 度だけ呼び出すと成功し、それ以外の場合は成功しません。

モックの作成を実装するための Flex Mock のコードは Mocha の場合とほとんど同じですが、メソッド名は少し異なります。リスト 9 はその違いを示しています。

リスト 9. Flex Mock を使ったモックの作成

document = flexmock("my document") document.should_receive(:print).times(1).and_return(false)

そしてリスト 10 は同じことを RSpec で行った場合を示しています。

リスト 10. RSpec を使ったモックの作成

document = mock("my document") document.should_receive(:print).and_return(false)

これらのモック作成用フレームワークにはかなりの数の修飾子を追加することができ、呼び出しパラメーターや、呼び出し回数に対する想定を指定することができます。スタブやモックを作成する対象となりうるのは、オブジェクト全体や、あるオブジェクトの 1 つのメソッド、あるいはクラスのメソッドなどです。私としては、少なくとも非推奨に関する議論が少し収まるまで、モックの作成には Flex Mock または Mocha を使うことをお勧めします。

まとめ

過去 2 年間、Ruby を積極的に使ってきた人であれば、高度なテストの概念に巨大な投資が行われていることに気付いたはずです。一部のテスト・ツールは昔からありますが、今や非常に多くの人がテスト・ツールに注目し始めています。熱狂的な Ruby 主義者がさらにテスト・ケースを開発するにつれ、モックを作成するような手法は、もっと一般的になるでしょう。

Ruby のコーディングを行うすべての人にとって、スタブやモックを作成する方法は貴重な方法です。「テスト・ファースト」主義の開発者は、完全に開発されていないインターフェースのモックを作成できる機能を便利だと思うでしょう。実行に時間のかかるテスト・スイートを抱えるプログラマーは、スタブとモックを使えばコストの高いインターフェースを除外することができます。すべてのテスターは、これらの手法を使ってテストに関する予測を立てられるようにする必要があります。

ここでは、Ruby のモック作成を行うライブラリーを延々と説明する代わりに、モック作成の背後にある概念を説明しました。また、一般的な 3 つのモック作成を行うライブラリーを簡単に調べました。これらの概念が初めての人は、まだまだ多くのことを学ぶ必要がありますが、先に進むための十分な基礎はできたはずです。いつものことですが、さらに学ぶための最善の方法はコーディングすることです。

ダウンロード可能なリソース

関連トピック