normalian blog

Let's talk about Microsoft Azure, ASP.NET and Java!

C# で private メソッドを呼んでみる

後でもうちょっと整形しよう・・・

privateメソッドにアクセスしたい!

単体テスト実行時等、privateメソッド、privateメンバにアクセスしたい事が多々あります。通常、一つのクラスに5つくらいメソッドがあったら、共通化処理のprivateメソッド or 状態保存用のprivateメンバが一個か二個は用意されているでしょう。
このprivateなメンバ・メソッドは割と単体テストの肝になると思いますが、もちろん単純にnewした程度ではこいつらにアクセスできません。今回はVisual Studio 2005以前、Visual Studio 2008 以降でオススメの方法をそれぞれ実行したいと思います。

今回のテスト用クラスのコード

class DarkSide
{
  private int state = 10;
  private int SumWithState( int num )
  {
    return state + num;
  }
}

Visual Studio 2005 以前(もちろん2008でも出来る)

基本的にはSystem.Reflection名前空間の機能を使って実現できます。メソッド、メンバ共に以下の形でアクセス可能です。

DarkSide target = new DarkSide();
int state = (int)target.GetType().InvokeMember("state",
  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, target, null);
int result = (int)target.GetType().InvokeMember("SumWithState",
  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, target, new object[] { 10 });

InvokeMemberメソッドはobject型で値を返すので、結果に応じてキャストが必要です。また、InvokMethodの引数は以下となります。

引数インデックス 用途
第一引数 メンバ名、メソッド名の文字列 string
第二引数 クラスへのアクセス用フラグ System.Reflection.BindingFlags
第三引数 引数のコンバータ用クラス System.Reflection.Binder
第四引数 制御対象インスタンス object
第五引数 メソッド引数 object[]

上記のBinderクラスについては下記URLのMSDNドキュメントを参照してください・・・。あまり使わないで有ろうクラスの割に、ドキュメントが結構長かったのでなえた…orz
BinderのMSDNドキュメント

Visual Studio 2008 以降

上記の様なリフレクション処理はもちろん単体テストで有効ですが、以下の様な問題も有ると思います。

  • 記述が冗長になる
  • 引数定義が面倒
  • 何よりメソッド名のリファクタに対応してくれない

これらの問題に対処するために、VisualStudio2008からテストフレームワークが導入(拡張?)されました。上記、DarkSideクラスに対して、以下の様な手順でアクセス可能です。

テスト対象クラスを右クリック → 「単体テストを作成」を選択

以下の右クリックメニューから選択して下さい

単体テストを作成したいメソッドを選択して「OK」ボタンを押す

単体テスト用のクラスが自動生成

以下は、若干手直しして、テストを二つに増やしました。以下のコードを見ていただければ分かると思いますが、<クラス名>_Accessorからはprivateだろうが平然とメソッド、メンバにアクセス可能になってます。

        [TestMethod()]
        [DeploymentItem("Doukaku.exe")]
        public void SumWithStateTest01()
        {
            DarkSide_Accessor target = new DarkSide_Accessor();
            int expected = 10;
            int actual;
            actual = target.state;
            Assert.AreEqual(expected, actual);
        }

        [TestMethod()] 
        [DeploymentItem("Doukaku.exe")]
        public void SumWithStateTest02()
        {
            DarkSide_Accessor target = new DarkSide_Accessor();
            int num = 20;
            int expected = 30;
            int actual;
            actual = target.SumWithState(num);
            Assert.AreEqual(expected, actual);
        }

さて、この<クラス名>_Accessorって何者なんだろう?という妄想にかられるので、クラス名を右クリックして定義を参照したところ下記のコードにたどり着きました。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Doukaku
{
    [Shadowing("Doukaku.DarkSide")]
    public class DarkSide_Accessor : BaseShadow
    {
        protected static PrivateType m_privateType;

        [Shadowing(".ctor@0")]
        public DarkSide_Accessor();
        public DarkSide_Accessor(PrivateObject __p1);

        public static PrivateType ShadowedType { get; }
        [Shadowing("state")]
        public int state { get; set; }

        public static DarkSide_Accessor AttachShadow(object __p1);
        [Shadowing("SumWithState@1")]
        public int SumWithState(int num);
    }
}

流石にぱっと中身を判断することは出来ませんが、各名前から推察するに以下な予想ができます。

  • BaseShadowクラス内部で存分にリフレクションをしている
  • Shadowing属性でprivateなメソッド、privateなメンバを弄る
  • 元のクラスをデコレータパターン的にラッピングしてる。

まとめ

  • リフレクションを使ってアクセスする方法は用途次第→単体テストに限らず使える
  • 単体テストであればAccessorを使え!
  • 出来ればリフレクションは使わないほうが良いかなぁ…。←ごちゃごちゃになるので・・・