[VB.NET] メソッド呼び出しをフックする

2013年11月25日


 テスト用のモックを作りたい時など、メソッドの内容をテスト用のものに差し替えたい場合があります。
 これを実現するために RealProxy を使用する方法があります。

 大前提として、以下の Import を追加しておきます。

Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Proxies
Imports System.Runtime.Remoting.Messaging

 まずターゲットとなるクラスを作成します。
 普段と異なるのは、MarshalByRefObject を継承しておく点です。

Public Class Class1
    Inherits MarshalByRefObject

    Public Function Read() As String
        Return "本物"
    End Function
End Class

 次に以下のプロキシークラスを作ります。
 ReturnMessage クラスが、メソッドの戻り値を作る部分です。
 今回は「偽物」を Return として戻す設定です。

Public Class Class1Proxy
    Inherits RealProxy

    Public Sub New()
        MyBase.New(GetType(Class1))
    End Sub

    Public Overrides Function Invoke(msg As IMessage) As IMessage
        Dim mm = CType(msg, IMethodMessage)

        Return New ReturnMessage("偽物!", Nothing, 0, mm.LogicalCallContext, mm)
    End Function
End Class

 これを以下の手順で呼びます。

Module Module1
    Sub Main()
        ' プロキシーを生成する
        Dim proxy = New Class1Proxy
        Dim p = proxy.GetTransparentProxy

        ' 作成されたプロキシーを、元のクラスにキャストする
        Dim a = CType(p, Class1)

        ' 呼んでみる
        Console.WriteLine("{0}", a.Read)
    End Sub
End Module

 しかしこのコードだと、呼ばれたメソッドが何であれ、必ず "偽物!" が戻ってしまいます。

 何のメソッドが呼ばれたかを確認するためには、前述 Class1Proxy.Invoke の mm.MethodBase.Name を使用することで可能です。
 本物のメソッドを呼びたい、という場合は、RemotingServices.ExecuteMessage を使用します。
 以上を踏まえると、Class1 のうち Read() のみを模擬で、他は本物のメソッド呼びたい場合、Class1Proxy は以下のように書けます。

Public Class Class1Proxy
    Inherits RealProxy

    Private _target As Class1

    Public Sub New(target As Class1)
        MyBase.New(GetType(Class1))

        ' 本物を呼び出す用のインスタンスを外部から受ける
        _target = target 
    End Sub

    Public Overrides Function Invoke(msg As IMessage) As IMessage
        Dim mm = CType(msg, IMethodMessage)

        ' メソッド名で分岐
        Select Case mm.MethodBase.Name
            Case "Read"
                ' 模擬で戻す
                Return New ReturnMessage("偽物!", Nothing, 0, mm.LogicalCallContext, mm)

            Case Else
                ' 本物を実行
                Return RemotingServices.ExecuteMessage(_target, mm)

        End Select
    End Function
End Class

 で、これでテスト用のモックが作れるかいうと… ちょっと面倒な気がします。
 理由は以下。

  1. Proxy クラス (上述では Class1Proxy) が難解。
  2. 実コードで使っている Class1 インスタンスを Class1Proxy インスタンスに差し替える必要がある。
  3. Public や Private ならなんとか差し替えられるが、ローカルスコープで使われている場合は手が出ない。
  4. ターゲットとなるクラスは MarshalByRefObject を継承する必要がある。

 個人的には 4. が鬼門。テストのためにリリースコードを汚す、というのがどうも好きになれないので。
 リリースコードを汚す前提なら、テスト用の Interface を切ったほうがスマートかな、という気がしていたりします。






カテゴリー: Program, VB.NET

Follow comments via the RSS Feed | Leave a comment | Trackback URL

コメントを投稿する

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


«   »
 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org