[C#]ユニットテストで private なメンバにアクセスしよう | 星空は撫子色
http://muu000.net/wordpress/?p=1691
実は、PrivateObject の存在を知らなかったんですよ。私の場合、テストしやすいように protected にしておくか、
Silverlight の UnitTest | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/330
PrivateObject クラス (Microsoft.VisualStudio.TestTools.UnitTesting)
http://msdn.microsoft.com/ja-jp/library/Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject.aspx
な感じで、internal にして、InternalsVisibleTo でアクセス許可を与えています。古い TDD の議論で private メソッドにアクセスするのは云々、という話もありますが、複雑怪奇なアルゴリズムを作るときは細かくメソッド分割をしてテストをしたほうがよいので、最終的に見せるのはひとつのメソッドだけど内部的には十数個のメソッドの組み合わせにしているというパターンがよくあります。F# の場合は内部関数を使って入れ子にしたりする訳で。まあ、最初は public で書いておいてテストが済んだら private に変換するってのが一番お手軽かと。
■リフレクションの GetTypeInfo().GetDeclaredField() を使う
MSDN のマニュアルを見ると、GetDeclaredField メソッドは public なフィールドを取得するはずなのですが、試してみると private フィールドも拾えます。なんででしょうね?例の WinRT から COM アクセスするときに、アクセスできるようになっちゃったのかもしれませんが、このあたり良く調べていません。
が、以下な感じで、プライベートなフィールドが取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public object GetField( string name) { Type t = this .RealType; var pi = t.GetRuntimeField(name); if (pi == null ) { // MSDN ではパブリックフィールドになっているが、プライベートも取得できる pi = t.GetTypeInfo().GetDeclaredField(name); } if (pi != null ) { var obj = pi.GetValue( this .Target); if (obj != null ) { return obj; } } return null ; } public T GetField<T>( string name) { object o = GetField(name); return o == null ? default (T) : (T)o; } |
一旦、GetRuntimeField で拾っているのは、public なフィールドをチェックしている訳で、いきなり GetDeclaredField で調べても構いません。元ネタの Microsoft.VisualStudio.TestTools.UnitTesting のほうは、戻り値が object だけだったので、型変換できるようにしています。
このコードは PCL 用なので GetRuntimeFiled を使っていますが、Native ライブラリの場合もそのまま使えます。
テストコードは、こんな感じです。
適当な private だらけなクラスを作っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /// <summary> /// for test sample class /// </summary> public class TestSample { private int privateInt = 10; private string privateStr = "masuda" ; private int _privateProp = 20; private int privateProp { get { return _privateProp; } } private int privateMethod( int x, int y) { return x + y; } public void init( int a, string b ) { privateInt = a; privateStr = b; } public int GetInt() { return privateInt; } public string GetStr() { return privateStr; } } |
それでもって、自作の PrivateObject でアクセスします。
1 2 3 4 5 6 7 8 9 10 11 12 | public void TestGetField() { var target = new TestSample(); var o = new D4Reflection.Lib.PrivateObject(target); target.init(1, "tomoaki" ); Assert.AreEqual(1, o.GetField< int >( "privateInt" )); Assert.AreEqual( "tomoaki" , o.GetField< string >( "privateStr" )); target.init(10, "masuda" ); Assert.AreEqual(10, o.GetField< int >( "privateInt" )); Assert.AreEqual( "masuda" , o.GetField< string >( "privateStr" )); } |
■プライベートなフィールド/プロパティ/メソッドにアクセスする
moonmile/D4Reflection
http://github.com/moonmile/D4Reflection
この中にある D4PrivateObject プロジェクトがそれですね。
http://github.com/moonmile/D4Reflection/blob/master/D4PrivateObject/PrivateObject.cs
これだけをコピーしても大丈夫です。
■何に使うのか?
Windows Store app の場合、x:Name で作ったオブジェクトは private なんですよね。だから、この方式を使っています。同じアセンブリならば FindName を使ってもいいわけですが、Xamarin.iOS/Android と共通に使うとなると結構厄介だったので、最終的に private フィールドを直で持ってきます。
そうそう、名前で検索するのは面倒なので、TypeProvider を使えないかなと…思ったけど C# だとうまく動かないわけで、そうなると T4 の出番ですかね。