Aug 11, 2010

モックが必要な場面、モックが有効な場面

モック(Mock)とスタブ(Stub)の違いがよく分かってなかったんだけど、何が違うのか、そしてモックはどう使う物なのかということを、すとうさんに教えてもらって今更理解した。あとで会社のブログに書くつもりだけど、メモとして要点だけまとめておく。

  • テストは基本的に、粒度の細かいブラックボックステストにした方がいい。
    • 内部に持ってる隠しプロパティの値が正しいかどうか?という風な実装べったりのテストは、実装の変更に非常に弱い。
    • なので、関数の返り値だけ見て検証できるような設計が、自動テストしやすい設計という意味で「良い設計」と言える。
  • しかしブラックボックステストには限界がある。
    • 色々な副作用を伴う機能だったり、非同期で処理するような機能だったり、1回の実行で複数の状態を遷移する機能だったり、という風に、単純に関数の実行→返り値を検証 とするだけではテストできない機能もある。
      • ユニットテストのレベルでそういう機能があるのは設計が良くない証拠なので、こういう物は関数を細かい単位に解体して、単純に関数の実行→返り値を検証 というブラックボックステストを行えるような設計に直すべき。
      • 処理待ちしてやりさえすればいいような場合、処理待ちのための機能を持ったテスティングフレームワークを使うと、単純な非同期処理なら簡単にブラックボックステスト化できる。
    • 色々な副作用を伴う機能や、状態遷移があるような機能をブラックボックステストできるようにしようと思うと、「内部で状態の遷移のログを取っておいて、最後にそのログの内容が期待通りになっているかどうかを検証する」という風な形にならざるを得ない。
      • ということをやろうと思うと、本番用のコードの中に「ログ取り用の処理」のような「実際に使う場面では無駄」なコードが増えていってしまう。
      • テストしやすくするためにテスト対象の実装に手を入れるのはよくあることだし、そうやって手を入れた結果として関数が小さな単位に分割されていったり関数名と入出力の対応が分かりやすくなっていったりするのなら、それによって動作が安定するようになったりコードのメンテナンス性が高くなったりするから、いいことだ。でも「テストのためだけの実装」が増えていって、動作が不安定になったりコードのメンテナンス性が落ちたりするのでは本末転倒だ。
  • ブラックボックステストにし続けるためのコストが、本来の実装に悪影響を及ぼすようなレベルになってしまったら、そろそろホワイトボックステストに移行していい頃合いだ。
    • 検証対象の機能の粒度が大きくなってくると、これはもう避けられない事と考えた方がいい。

自分は今まで、とりあえずユニットテストに注力していて、ある意味脅迫観念的な勢いで、ブラックボックス度合いを高くする事を心がけてた。今まではそれでだいたい問題なかった。でも最近になって、ブラックボックステストにしようとすると無理があるというケースにぶち当たるようになった。1つの機能の中でコロコロと遷移する内部状態を、どうにかして検証したいというようなケースが出てきた。

それですとうさんに相談したら、そういう時はモックを使えばいいと言われた。でも、話を聞く限りだとモックというのはテスト対象の実装の中の処理の流れを追う物のようなので、それじゃブラックボックステストにならないじゃないかと思った。それをそのまま言ったら、確かにテストはできるだけブラックボックステストになってた方がいいけど、機能テストやインテグレーションテストのような粒度の大きな単位のテストでは、処理の中で起こる様々な出来事や副作用を色々モニタリングして、すべての処理が期待通りに動いているかどうかを検証しないといけないから、必然的にホワイトボックステストにならざるを得ないと言われた。

それを聞いて、目の覚めるような思いをした。そうか、ブラックボックステストとホワイトボックステストの使い分けはそこが基準になるのか、と。今まで自分がホワイトボックステストを書かずに済んでいたのは、状態の遷移を伴うような機能を作る必要がなかったからだったんだ、テストをどうも書きにくいなあと思っていた機能は本当はホワイトボックステストにしたほうがいい物だったんだ、と。

そんなわけでUxUにモックの機能を実装した

「JavaScript Mock」で検索するとJSMockjqmockが上位に出てきたので、最初はそれらを参考にするように(メジャーな実装があるんだったらそれをそのまま取り入れるなりAPIを合わせるようにするのが望ましい)と言われたんだけど、ドットで繋げるメソッドチェインの記法がガンガン出てきて頭パンクした。

もう少し下の方までスクロールするとMockObject.jsというのが出てきて、こっちはファイル全体で3KBに満たない小さなライブラリなので、まずはここから始めることにした。何せコードが短いから、読むのもそんなに苦にはならない。モックの概念を言葉で説明されてもさっぱりだったけど、一通りの処理の流れを見たら、「モックというのは一体何をやらなきゃいけないのか」「どういう振る舞いが期待されているのか」ということがよく分かった。

MockObject.jsと同等の機能を一通り実装した後でもう一度JSMockの方を見たら、なるほどこれはこういう意味だったのかというのがやっと分かった。サンプルコードを見ても、モックという物の意味をそもそもよく知らない時点では、どこからどこまでがJSMockの部分なのかさっぱり分からなかったんだよね。ということで、MockObject.jsに加えてJSMock互換のAPIも付け加えてみた。jqunitの方は……もう別言語だからシラネ。

あと有名なのはJsMockito? これも頑張ったらできるかなあ、というかMITライセンスだしそのままぶち込んだ方が早いか……

エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能