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