如何用FsUnit检查歧视联盟的情况?

时间:2013-09-25 09:49:39

标签: f# discriminated-union fsunit

我想检查某个值是否是受歧视联盟的特定情况,而不必检查任何包含的数据。我的动机是每次单元测试只测试一件事。

示例如下(最后两行给出编译错误):

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但不想为每种情况编写匹配的函数(在我的实际代码中,远远超过两种)。

4 个答案:

答案 0 :(得分:8)

如果您不介意使用反射,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"

请注意,这不是类型安全的,您可以轻易拼错工会案例名称。

我要做的是创建一个类似的DU用于比较目的:

type MyStateCase =
    | StateOneCase
    | StateTwoCase

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

通过这种方式,您可以定义categorize一次并多次使用它。

increment state
|> categorize
|> should equal StateTwoCase

答案 1 :(得分:2)

看来FSUnit没有(或者不能,我不确定)直接支持这个用例。

我发现的下一个最好的事情是声明一个TestResult类型,如下所示,并使用匹配将结果减少到此类型。

type TestResult =
| Pass
| Fail of obj

这是减少匹配

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

现在您可以使用should equal来确保正确的结果。

testResult  |> should equal Pass

此解决方案的好处是强类型,但更重要的是在失败的情况下,您可以看到无效结果

答案 2 :(得分:0)

它看起来不太优雅,但你可以从状态值中提取类型:

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

然后在测试中使用它:

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

<击> 修改

是的,不幸的是,类型总是 MyState 。看起来模式匹配或难看的反射是不可避免的。

答案 3 :(得分:0)

如果FsUnit已经支持针对特定联合案例的断言,但是只限于Microsoft.FSharp.Core.Choice<_,...,_>类型的值,该怎么办?

让我们利用多案例活动模式来利用它,它使用Reflection来检查联合案例名称。

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

现在应该正常工作:

increment state
|> (|Pass|Fail|) "StateTwo"
|> should be (choice 1)