2014年11月23日日曜日

デフォルトの振る舞い - from "Prig: Open Source Alternative to Microsoft Fakes" Wiki -

まとまってきたドキュメントを日本語の記事にもしておくよシリーズ第 4 段!(元ネタ:Prigwiki より、FEATURES: Default Behavior。同シリーズの他記事:1 2 3

自動化されたテストで既存の振る舞いを囲う時、そのメソッドが呼び出されているかどうかだけを、単にチェックしたくなることがあると思います。例えば、「対象のメソッドが、現在の環境やプラットフォームの情報にアクセスしているかどうかを知りたいので、クラス Environment のいずれかのメソッドの呼び出しを検出したい」というようなことが考えられますね。これを実現するために、Prig では間接スタブのデフォルトの振る舞いを変更する機能をサポートしています。今回はこの機能を解説していきましょう。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
Muhammad Shujaat Siddiqi Prefer 32-Bit with Any CPU Platform Target - Visual Studio 2012 Enhancements
何かのときにすっと出したい、プログラミングに関する法則・原則一覧 by @hiroki_daichi on @Qiita
OpenTouryoProject/OpenTouryo - GitHub
Code Generation in a Build Process
Microsoft.Fakes stub interface fails to be found - Stack Overflow
Microsoft Fakes: Trying to shim a class but dependencies are still there - Stack Overflow
c# - How do you call a constructor via an expression tree on an existing object? - Stack Overflow
Why did we build
rspec の書き方がわからない"" - Togetterまとめ
Myron Marston » Notable Changes in RSpec 3





目次

デフォルトの振る舞い
例として、以下のような「古き良き時代の」コードに対して、仕様の変更が発生した、ということを想像してみてください - もちろん、テストコードはありませんね (´・ω・`)

NotifyStartJob は、最初に、あるジョブを実行するための前提条件を検証し、全ての条件が満たされていれば、ジョブに引き渡すパラメータファイルを生成します。goto 文を使って、共通のエラー処理に飛ばす手法は、C 言語が主流だった時代によく見られたものですが、例外による通知が標準になった現在でも、そのエラーが本当の例外でなく単に業務的なエラーだったり、パフォーマンスの問題があったりすれば、まだ利用されることがあるかもしれません。

ちょっと簡単ではないということを感じたあなたは、テストコードを書くことにしました。まずは、古くからあるモックフレームワークとちょっとの修正で可能な範囲で、その振る舞いを囲うことにします。プロダクトコードを見回すと、CommunicationContext は構造体である必要はなさそう。クラスに変更し、メンバーを virtual にすることにしました:

メンバーが static であることも止めたいですが、それはできません。なぜなら、他の多くのメンバから参照されてしまっているためです。仕方がないので、ここで、Prig を使い間接スタブを作成することにします。Package Manager Console で、以下のコマンドを実行してスタブ設定を作成し・・・:

以下のコマンドで、UpdateJobParameterFile の設定をクリップボードにコピーし、スタブ設定(例えば、GofUntestable.v4.0.30319.v1.0.0.0.prig)に追加します:


上記の準備によって、全てのテストが書けるようになります。例えば、以下のようなケースが書けると思います:

ところで、今回の仕様変更は何だったかというと、「ライセンスによる検証を削除する」というものでした。具体的には、「VerifyProductLicense はもう呼ばれない」ということを実装すれば良いことになります。以下のようにテストの Assert の一部を書き換え、テストが失敗することを確認します:

あとは、テストが通るようにプロダクトコードを変更するだけですね!あなたは安心して、JobManager を以下のように書き換え、テストが成功することを確認します。簡単な仕事でしたね?

・・・アイヤー、これはヒドイ。問題に気づきましたか?これでは常に goto fail; へ行ってしまい、ジョブに渡すパラメータファイルが常に作成されてしまいます!加えて、残念なことに、JobManager は err を code として、UpdateJobParameterFile に 設定していますので、最悪ジョブが常に実行されることになるでしょう。今年 2 月にあった Apple 史上最悪のセキュリティバグが思い出されますね。

このような問題を防止するには、意図しないメソッドが呼び出されたら例外を投げるようにします。この例では、fail ラベルで、問題があった環境の情報をログに書き込む処理があり、Environment の各プロパティを参照しているようです。従って、それらが呼び出された時に例外をスローするのが良いでしょう。Environment は mscorlib に属すクラスで、ほとんどのメンバが static です。この場合、Prig を使って間接スタブを作成する以外に、選択の余地はありませんね:

上のコマンドを実行すると、mscorlib の間接スタブ設定を作成することができます。そうしましたら、Environment の public static なメンバーの間接スタブを作成しましょう(以下のコマンドの結果を mscorlib.v4.0.30319.v4.0.0.0.prig に貼り付けます):

さて、これで Environment の public static なメンバーに対して、一度にデフォルトの振る舞いを変更できるようになりました。テストコードに、以下の変更を加えます:

使用しているモックフレームワークによっては、既に予約されているメンバーがあるため、Environment のいくつかのメンバーを除外することがちょっとメンドクサイ(Moq のケースだと、上記の通り、Environment.CurrentManagedThreadId や Environment.OSVersion がそれに当たります)。ですが、これで意図しないメソッドの呼び出しを監視することができるようになりました。NotifyStartJob に対する上記の修正では、もはやテストが通らないことがわかると思います。これで本当に安全になったわけですね!

ちなみにこの機能、テストケースを書く時、モックのされ具合を確認することや、複雑な条件下にある外部アクセスから守るガードとしても利用ができます。また、IndirectionBehaviors.DefaultValue を設定すれば、デフォルト値を返すようなデフォルトの振る舞いに変更することもできます。レガシーコードにおいては、1 つのメソッドで何度も何度も void Foo() のようなシグネチャのメソッドが呼び出されている状況(中でメンバ変数 or グローバル変数ガシガシ変えてる!!!)も少なくないでしょう。しかし、テスト中のもの以外を何もしない振る舞いに設定すれば、簡単に観点を絞り込むことができるようになるはずです。



0 件のコメント:

コメントを投稿