2014年12月8日月曜日

非公開メソッドの入れ替え - from "Prig: Open Source Alternative to Microsoft Fakes" Wiki -

ソフトウェアテストあどべんとかれんだー2014 8日目!&まとまってきたドキュメントを日本語の記事にもしておくよシリーズ第 9 段!(元ネタ:Prigwiki より、FEATURES: Non Public Method Replacement。同シリーズの他記事:1 2 3 4 5 6 7 8

はじめましての方ははじめまして!ソフトウェアテストあどべんとかれんだー2014 8日目を担当させていただきます、@urasandesu こと杉浦と申します。

前日は、@hayabusa333 さんの、Ruby - Gauntltによるセキュリティテスト #SWTestAdvent - Qiita でしたね。セキュリティテスト自動化フレームワーク、そんなものもあるのだと興味深く拝読させていただきました。2014 年は、Heartbleed や、Apache Struts の脆弱性ShellshockPOODLE と、脆弱性の話題に事欠かない年になってしまいましたが、今後セキュリティに関するテストはますます重要になっていくんでしょうね。

さて、ソフトウェアテストに関連することということで、私からは打って変わって実装寄りのお話を。以前から私が作成しています Prig という、.NET 自動ユニットテスト向け迂廻路生成ライブラリについて、紹介させていただこうと思います。迂廻路生成?と思われるかもしれませんが、簡単に言うと、通常は行えない static メソッドや private メソッドの上書きをできるようにするというものです(.NET だと Microsoft Fakes、Java だと JMockit とかが有名どころでしょうか)。

題材は「既存ライブラリの非公開メソッドが絡む自動ユニットテスト」。言語は C# です。よく言われる通り、非公開なメソッドそのものをテストすることはよくないこととされていますが、テストで非公開なメソッドに対して何かしたくなることは、しばしばあるんじゃないでしょうか?例えば、テスト対象のメソッド内で使われる private な setter を持つプロパティに意味のある値を与えておきたいWeb にアクセスしにいってしまう private メソッドをモックに入れ替えたい、などなど。まあ、今まさに開発中のコンポーネントであれば、いくらでも対処方法はあるのですが、すでに稼働しているシステムだったり、外部から買い入れたコンポーネントだったりすると、途端に難易度が跳ね上がるのが困りもの。

ところで、C# の特徴的な機能の 1 つに、今から 7 年ほど前に出た C# 3.0 で追加された、拡張メソッドという機能があります。皆さんは拡張メソッドは好きですか?乱用するべきではないですが、その機能が、本質的に、そのライブラリがそのレイヤーでサポートしてほしいものであれば、設計上自然な API を実現できることがあるかと、私は思います。ただし、そのライブラリが、そのような拡張に対してオープンであるかどうかは、場合によるでしょう。特に、そのシグネチャに、非公開な属性の 1 つである internal なクラスが現れるようなメソッドが関係する場合は要注意。今回は、Prig によって、どのようにこの問題を解決するかを解説したいと思います。


以下の記事、ライブラリを使用/参考にさせていただいています。この場を借りてお礼申し上げます m(_ _)m
How to mock ConfigurationManager.AppSettings with moq - Stack Overflow
TDD, Unit testing and Microsoft Fakes with Sitecore Solutions
Basic mocking techniques - Stack Overflow
Hybrid Framework - http://our.umbraco.org
Unit testing Umbraco 7 | just this guy
How to Write 3v1L, Untestable Code
Generic Methods Implementation in Microsoft Fakes - CodeProject
Paulo Morgado - Mastering Expression Trees With .NET Reflector
Expression Tree Visualizer for VS 2010 - Home
mocking - Using Microsoft Fakes Framework with VSTO Application-Level Add-in an XML based Ribbon - Stack Overflow





目次

非公開メソッドの入れ替え
既存ライブラリに、以下のような DTO 群があるとしましょう:

「DB への接続」という副作用と、「テーブル自体のデータ」という状態を 1 つのクラスで管理しており、嫌な臭いを感じます。ただ、このライブラリを作ったニンゲンが、これ以上のリファクタリングをするモチベーションを持つことはないかもしれません。なぜならば、このライブラリだけ見れば internal なクラスが緩衝材としてあり、InternalsVisibleToAttribute を使えば、制限なくそのクラスにアクセスができるため、テストをするのに特に問題を感じないでしょうから。

さて、このライブラリはテーブルスキーマの自動生成ツールも提供しており、特定の列を自動生成してくれます。そのような列は、以下のような規約で命名されるとのことです:
  • <table name> + _ID ・・・ プライマリキー
  • DELETED ・・・ 論理削除フラグ
  • CREATED ・・・ 作成日時
  • MODIFIED ・・・ 更新日時
なるほどなるほど。そうすると「自動生成された列だけを取得する」や「手動で生成された列だけを取得する」などのようなことがやりたくなりますね。残念ながら、既存のライブラリは、そのような機能を提供していないとのこと。なので、今回は自分で作成することにしました。こんなシチュエーションでは拡張メソッドがピッタリでしょう。テストを書いてみます:

テーブル USER には、列 USER_ID、PASSWORD、USER_NAME、DELETED、CREATED、MODIFIED があるとします。そのテーブルに対し、拡張メソッド GetAutoGeneratedColumns を実行すると、自動生成された列が取得できるという寸法です。このままではビルドすら通りませんので、とりあえず以下のような最低限の実装を用意しました:

ほい、実行っと。NotImplementedException がスローされるでしょうから、とりあえずなコードを追記し・・・て・・・あれ?

ヴァー!変なとこで引っかかっとる!! ('A`)

実は、スタックトレースにも出力されているように、ULColumns は、メソッド ValidateState を使うことによって、列が変更可能かどうかを検証しています。テストケースにおいては、GetAutoGeneratedColumns を検証したいのですが、その前、users.Columns.Add(expected[0]); で例外がスローされていたわけですね。これはいけません・・・。

このような状況で、Prig を使うことで、不必要な検証を一時的に外すことができます。Prig をインストールし、その Assembly のスタブ設定を追加します:

以下のコマンドを実行し、ULColumns の設定をクリップボードにコピーします:

そうしましたら、追加されたスタブ設定ファイル(例:UntestableLibrary.v4.0.30319.v1.0.0.0.prig)にそれを貼り付け、ソリューションをビルドします:

上の準備が終わったら、以下のようにテストを書き直すことができるようになります:

こんどはどうでしょう?

よし!今度は NotImplementedException がスローされるという、意図した結果になりました。後は、テストを通すコードを書き、全てのテストが通ったらリファクタリングをし、新たなテストを追加する、という黄金の回転を回すだけ。良い感じじゃないですか?





付録
ところで、かの人が元のライブラリをどのように設計していれば、もっと簡単にテストができていたと思いますか?そもそも、状態を副作用から分離したライブラリとして再設計すべきだとは思いますが、既存のライブラリで、インターフェイスを変更するような再設計は難しいでしょうね。せめてなにかできることがあるとすれば、非 public なインターフェイスを public にするような変更ぐらいでしょう。

なお、次の点には注意してください:単にインターフェイスを公開する、例えば、ULTableStatus の全てのフィールドと、ULColumns のコンストラクタ .ctor(ULTableStatus) を公開するようなことをしてしまえば、簡単にデータの不整合が起きるようになってしまい、ライブラリが安全ではなくなってしまいます。ライブラリの安全性が保たれる範囲のインターフェイスだけを公開するべきでしょう。このケースでは、以下のような変更が考えられます:

ULColumns のコンストラクタは公開しましたが、ULTableStatus を直接指定することはせず、検証のためのメソッドだけを持ったインターフェイス IValidation を代わりに指定するようにしました。Prig を使って入れ替えたかったメソッド ValidateState が持つ機能を、外出ししたことになります。対象のメソッドは状態にアクセスはしますが、それを書き換えることはしません。従って、その部分を公開するだけであれば、ライブラリのデータの整合性は保ち続けられることになります。それから、ULTable の ULColumns を生成するプロパティを virtual 化します。

これらの再設計により、Prig を使わなければキーとなるメソッドに到達することすら難しかったテストは、以下のようにできます。Moq のような通常のモックフレームワークで、簡単にテストができるようになるのです:





終わりに
ソフトウェアテストあどべんとかれんだー2014 8日目、C# と自作ライブラリを題材に、自動ユニットテストにおける非公開メソッドの入れ替えを解説してみました。つい先月、V1.0.0 をリリースしたばかりということもあり、まだまだ問題もあるかと思いますが、もし興味を持っていただき、使っていただければ嬉しいです!問題などあれば、是非 @urasandesu 宛てにお気軽に mention 下さいませ (((o(*゚▽゚*)o)))

さて、私のまとまってきたドキュメントを日本語の記事にもしておくよシリーズ(1 2 3 4 5 6 7 8)はこれで終わりですが、ソフトウェアテストあどべんとかれんだー2014 はまだまだ続きますのでお見逃しなく!

明日は、@PoohSunny さん。よろしくどうぞ!!

0 件のコメント:

コメントを投稿