我想检查某个值是否是受歧视联盟的特定情况,而不必检查任何包含的数据。我的动机是每次单元测试只测试一件事。
示例如下(最后两行给出编译错误):
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但不想为每种情况编写匹配的函数(在我的实际代码中,远远超过两种)。
答案 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)