2013-09-25 5 views
6

私は、値が、含まれているデータをチェックする必要もなく、識別されたユニオンの特定のケースであることを確認したいと思います。私のモチベーションは、単体テストごとに1つだけテストすることです。次のようにFsUnitとの識別されたユニオンの大文字小文字を確認するにはどうすればよいですか?

例は、(最後の2行は、コンパイルエラーを与える)である:

module MyState 

open NUnit.Framework 
open FsUnit 

type MyState = 
    | StateOne of int 
    | StateTwo of int 

let increment state = 
    match state with 
    | StateOne n when n = 10 -> StateTwo 0 
    | StateOne n -> StateOne (n + 1) 
    | StateTwo n -> StateTwo (n + 1) 

[<Test>] 
let ``incrementing StateOne 10 produces a StateTwo``()= 
    let state = StateOne 10 
    (increment state) |> should equal (StateTwo 0)    // works fine 
    (increment state) |> should equal (StateTwo _)    // I would like to write this... 
    (increment state) |> should be instanceOfType<StateTwo> // ...or this 

が、これはFsUnitで行うことができますか?

私はthis answerを認識していますが、それぞれのケースで一致する関数を書く必要はありません(実際のコードでは2つ以上のものがあります)。

+0

が実際のC#からこれを行うには合理的に簡単な方法がありますが、それはF#で動作しません。

open System.Reflection open Microsoft.FSharp.Reflection let (|Pass|Fail|) name (x : obj) = let t = x.GetType() if FSharpType.IsUnion t && t.InvokeMember("Is" + name, BindingFlags.GetProperty, null, x, null) |> unbox then Pass else Fail x 

は今働いされなければなりません。 –

答えて

7

あなたは反射を使用して気にしない場合は、this answerからisUnionCase機能は便利かもしれない:

increment state 
|> isUnionCase <@ StateTwo @> 
|> should equal true 

(注)値を比較する前に、関数呼び出しを必要とするので、それは少し冗長だということ。

似ていますが、軽量化のアプローチは、タグの比較可能性:

// Copy from https://stackoverflow.com/a/3365084 
let getTag (a:'a) = 
    let (uc,_) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(a, typeof<'a>) 
    uc.Name 

increment state 
|> getTag 
|> should equal "StateTwo" 

が、これは安全な型ではなく、あなたが簡単に組合ケース名のスペルを間違えできることに注意してください。このようにし

type MyStateCase = 
    | StateOneCase 
    | StateTwoCase 

let categorize = function 
    | StateOne _ -> StateOneCase 
    | StateTwo _ -> StateTwoCase 

、一度categorizeを定義し、それを複数回使用します。私はどうなるのか

は、比較目的のために同様の川下ユーザーを作成することです。

increment state 
|> categorize 
|> should equal StateTwoCase 
0

それは非常にエレガントに見えるしませんが、状態の値からタイプを抽出することができます。

let instanceOfState (state: 'a) = 
    instanceOfType<'a> 

そして、テストでそれを使用します。

(increment state) |> should be (instanceOfState <| StateTwo 88) 

EDIT

はい、残念ながらタイプは常にMySですtate。パターンマッチングや醜い反射が避けられないように見えます。

+0

これは動作していません - 私は 'StateOne 88'を使ってもテストに合格しますので、値が' StateTwo'であることを確認しません。 –

+1

'' a''は 'MyState'だけなので、これは動作しません。 'instanceOfState'が' state'パラメータに関する実行時の型情報を使用した場合には動作しますが、それは少し違った動作が必要なことを意味します... –

1

FSUnitはこのユースケースを直接サポートしていません(またはわかりません)。

私が見つけた次善策は、次のようなタイプのTestResultを宣言し、このタイプの結果を減らすためにマッチを使用することです。ここで

type TestResult = 
| Pass 
| Fail of obj 

は今、あなただけの正しい結果を保証するためにshould equalを使用することができます減少試合

let testResult = 
    match result with 
    | OptionA(_) -> Pass 
    | other -> Fail(other) 

です。

testResult |> should equal Pass 

このソリューションの利点は、厳密な型指定されているが、より重要なこと障害ケースで無効な結果がだったかを見ることができます。

0

Microsoft.FSharp.Core.Choice<_,...,_>の値に制限されているにもかかわらず、FsUnitが特定のユニオンの大文字と小文字を区別してアサーションをサポートしている場合はどうなりますか?

これをマルチケースのアクティブパターンで活用しましょう。このパターンでは、Reflectionを使用してユニオンのケース名をチェックします。

increment state 
|> (|Pass|Fail|) "StateTwo" 
|> should be (choice 1) 
関連する問題