テスト用のモックを作りたい時など、メソッドの内容をテスト用のものに差し替えたい場合があります。
これを実現するために 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
で、これでテスト用のモックが作れるかいうと… ちょっと面倒な気がします。
理由は以下。
- Proxy クラス (上述では Class1Proxy) が難解。
- 実コードで使っている Class1 インスタンスを Class1Proxy インスタンスに差し替える必要がある。
- Public や Private ならなんとか差し替えられるが、ローカルスコープで使われている場合は手が出ない。
- ターゲットとなるクラスは MarshalByRefObject を継承する必要がある。
個人的には 4. が鬼門。テストのためにリリースコードを汚す、というのがどうも好きになれないので。
リリースコードを汚す前提なら、テスト用の Interface を切ったほうがスマートかな、という気がしていたりします。