9

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

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

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 中完成吗?

我知道这个答案,但不想为每种情况编写匹配函数(在我的真实代码中,有两个以上)。

4

4 回答 4

9

如果您不介意使用反射,则此答案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
于 2013-09-25T13:10:17.030 回答
3

似乎 FSUnit 不(或者不能,我不确定)直接支持这个用例。

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

type TestResult =
| Pass
| Fail of obj

这是减少匹配

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

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

testResult  |> should equal Pass

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

于 2016-11-24T05:23:37.870 回答
0

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

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

然后在测试中使用它:

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

编辑

是的,不幸的是,类型始终是MyState。看起来模式匹配或丑陋的反射是不可避免的。

于 2013-09-25T11:51:07.190 回答
0

如果FsUnit已经支持针对特定联合案例的断言,尽管仅限于 type 的值Microsoft.FSharp.Core.Choice<_,...,_>怎么办?

让我们利用多案例活动模式来利用这一点,该模式使用反射来检查联合案例名称。

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)
于 2016-11-24T20:38:03.197 回答