ようこそ。睡眠不足なプログラマのチラ裏です。

F#でもMoq使いたかったんだけど、なんかダメそうなのでRhino Mocksを採用しました。

タイトル通り、F#でもMoq使いたかったんだけど、なんかダメそうなのでRhino Mocksを採用しました。というメモ。
それ以上でもそれ以下でもありません。


F#でMoq利用を試みるも撃沈

結論から言うと、F#とMoqの相性は最悪。
Moqは、F#で全く利用できないわけでもないけど、いろいろと残念な感じになってしまいました。
下記サンプルでは、F# PowerPackおよび単体テストフレームワークNUnitとFsUnitを利用しています。


moq
http://code.google.com/p/moq/


FsUnit
http://fsunit.codeplex.com/
NUnitの機能を使うのでNUnitも別途必要です。

module Program
open System
open System.Runtime.CompilerServices;
open System.Linq.Expressions
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation
open Moq
open Moq.Language
open System.Diagnostics
open NUnit.Framework
open FsUnit
open MoqExtentions

type IGreeting = 
  abstract Greet : string -> string

[<TestFixture>]
type ``Moq Test Sample`` ()=
   
   [<Test>] 
   member x.ICloneableTest () = 
     let mock = new Mock<ICloneable>()
     let clone = new Object ()
     mock.setupFunc (<@ Func<_, _>(fun x -> x.Clone ()) @>) clone 
       |> ignore 
     mock.Object.Clone () |> should equal clone
     mock.VerifyAll ()

   [<Test>] 
   member x.IDisposableTest () = 
     let mock = new Mock<IDisposable>()
     use d = mock.Object 
     d.ToString () |> Console.WriteLine  
     ()
     mock.VerifyAll ()

   [<Test>] 
   member x.IGreetingTest () = 
     let mock = new Mock<IGreeting>()
     mock.setupFunc (<@ Func<_, _>(fun x -> x.Greet (It.IsAny<string>())) @>) "おっはー" 
       |> ignore
     let greet = mock.Object.Greet "おはようございます"
     greet |> should equal "おっはー"
     mock.VerifyAll ()

let Main() =    
    try
        let test = ``Moq Test Sample`` ()
        test.ICloneableTest ()
        test.IDisposableTest ()
        test.IGreetingTest ()

        printfn "complete."
    with
        | :? NUnit.Framework.AssertionException as ex -> printfn "Error:%s" ex.Message
        | _ as ex -> printfn "%s" ex.Message 

[<STAThread>]  
[<assembly: InternalsVisibleToAttribute("DynamicProxyGenAssembly2")>]
do Main() 
   |> Console.ReadLine 
   |> ignore


Moqでセットアップするためには、F#な式木からLinqExpressionへの変換が必要。
面倒事はType Extensionsに別定義してみたり。

module MoqExtentions
open System
open System.Linq.Expressions
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation

type Moq.Mock<'T when 'T : not struct> with 
  member public x.setupAction (quot : Expr<Action<'T>>) =
    let expr = (quot.ToLinqExpression () :?> Expression<Action<'T>>)
    x.Setup(expr)
  member public x.setupFunc (quot : Expr<Func<'T, 'TResult>>) (returns : 'TResult) =
    let expr = (quot.ToLinqExpression () :?> Expression<Func<'T, 'TResult>>)
    x.Setup(expr).Returns returns 


実行結果

Castle.Proxies.IDisposableProxy
アセンブリ 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' 
から型 'Castle.Proxies.IGreetingProxy' はアクセスできないインターフェイスを実装しようとしています。


MoqはItクラスによる引数の条件指定あたりが割と使い勝手がよく結構お気に入りだったので、
F#でもうまく利用できたらいいなと思っていたのだけど、上記のように非常に残念な結果となりました。
仮にうまく利用できたとしても、式木生記述でモックのセットアップをするというのは少々抵抗がある。
F# PorwrPackのQuotationsを利用することで容易に式木を記述できるとはいえ、
これはあまり美しとは言えませんからね。いまのところF#とMoqの相性は宜しくないようです。
Moqのメジャーバージョンアップに期待…。


F#でRhino Mocksを利用する

ということで、他に.NETでよく利用されているモックオブジェクトフレームワークも試してみることに。


.NET向けのモックオブジェクトフレームワークもいろいろとありますが、
やはり有名所が無難かなということで、多くの.NET開発者に支持されているRhino Mocksで試してみました。
こちら、知らないうちにかなりバージョンアップしていたようで、過去バージョンのとは別物っぽくなっている。
バージョン3.5以降では、Lamba ExpressionsおよびExtension Methodsを使用するための
新たなArrange、Act、Assert構文なんかが増えていたようで、なかなかいい感じのモックオブジェクトフレームワークに成熟していたようです。
下記サンプルでは、単体テストフレームワークNUnitとFsUnitを利用しています。F# PowerPackは利用していません。

namespace Test
open System
open NUnit.Framework
open Rhino.Mocks
open FsUnit

[<TestFixture>]
module TestSample =
  type IGreeting = 
    abstract Greet : string -> string
    abstract member morningReply : string with get, set 
    abstract member afternoonReply : string with get, set 

  [<TestFixture>]
  type ``Rhino Mocks Test Sample`` () =

    [<SetUp>] 
    member x.startUp () = ()

    [<TearDown>] // 
    member x.tearDown () = ()
   
    [<Test>] 
    member x.ICloneableTest () = 
      let mock = MockRepository.GenerateMock<ICloneable>()
      let clone = new Object ()
      RhinoMocksExtensions.Stub<ICloneable, Object>(mock, fun x -> x.Clone ()).Return(clone) 
        |> ignore
      mock.Clone () |> should equal clone
      RhinoMocksExtensions.VerifyAllExpectations(mock)

    [<Test>] 
    member x.IDisposableTest () = 
      let mock = MockRepository.GenerateMock<IDisposable>()
      use d = mock 
      ()   

    [<Test>] 
    member x.IGreetingTest () = 
      let mock = MockRepository.GenerateMock<IGreeting>()
     
      RhinoMocksExtensions.Expect<IGreeting, string>(mock, fun x -> x.morningReply).Return("おっはー")
        |> ignore
      RhinoMocksExtensions.Expect<IGreeting, string>(mock, fun x -> x.afternoonReply).Return("ちーっす")
        |> ignore
    
      RhinoMocksExtensions.Expect<IGreeting, string>(mock, fun x -> x.Greet "おはようございます").Return(mock.morningReply)
        |> ignore
      RhinoMocksExtensions.Expect<IGreeting, string>(mock, fun x -> x.Greet "こんにちは").Return(mock.afternoonReply)
        |> ignore

      let morning = mock.Greet "おはようございます"
      morning |> should equal "おっはー"
      let afternoon = mock.Greet "こんにちは"
      afternoon |> should equal "ちーっす"
      RhinoMocksExtensions.VerifyAllExpectations(mock)

  let Main() =    
      try
          let test = ``Rhino Mocks Test Sample`` ()
          test.ICloneableTest ()
          test.IDisposableTest ()
          test.IGreetingTest ()

          printfn "complete."
      with
          | :? NUnit.Framework.AssertionException as ex -> 
            printfn "test failure:%s" 
             (ex.Message + System.Environment.NewLine + ex.StackTrace) 
          | _ as ex -> printfn "other exception:%s" 
                        (ex.Message + System.Environment.NewLine + ex.StackTrace)  

  [<STAThread>]  
  do Main() 
     |> Console.ReadLine 
     |> ignore


実行結果

complete.


うむ。実はぜんぜんRhino Mocksのドキュメント読んでないけど、インテリセンスにしたがって導かれるまま書くだけで、
違和感なく普通に使えちゃった。これはいい感じw。F#とRhino Mocksの相性はなかなかヨサゲ。採用です。
ところで、Rhino MocksにはMoqのItクラス的な機能ってあるのかな?ドキュメントは、そのうち読もう・・・と思う。


Rhino Mocks
http://ayende.com/Blog/archive/2009/09/01/rhino-mocks-3.6.aspx
http://www.ayende.com/projects/rhino-mocks/downloads.aspx


Rhino Mocks Documentation
http://www.ayende.com/wiki/Rhino+Mocks+Documentation.ashx