在F#与FsUnit中使用should equal比较序列时的异常问题
我使用FsUnit.Xunit编写测试时遇到了异常:明明预期和实际的序列元素看起来一致,测试却始终失败。
测试代码:
[<Fact>] let ``Initialization of DFF`` () = dff Seq.empty Seq.empty |> should equal (seq {Zero})
测试失败信息:
Message:
FsUnit.Xunit+MatchException : Exception of type 'FsUnit.Xunit+MatchException' was thrown.
Expected: Equals seq [Zero]
Actual: seq [Zero]Stack Trace:
That.Static[a](a actual, IMatcher`1 matcher)
Signal.Initialization of DFF() line 11
将预期值改为Seq.singleton Zero后,错误依旧。但如果把序列转为列表再做比较,测试就能正常通过。我希望不转换类型就能完成序列相等性验证,想知道问题出在哪。
可复现的完整源代码:
type Bit = | Zero | One type Signal = seq<Bit> let Nand a b = match a, b with | Zero, Zero -> One | Zero, One -> One | One, Zero -> One | One, One -> Zero let Not input = Nand input input let And a b = Not (Nand a b) let Or a b = Nand (Not a) (Not b) let private liftToSignal1 op (signal: Signal) : Signal = Seq.map op signal let private liftToSignal2 op (signalA: Signal) (signalB: Signal) : Signal = Seq.map2 op signalA signalB let Not' = liftToSignal1 Not let And' = liftToSignal2 And let Or' = liftToSignal2 Or let rec dff data clock : Signal = seq { yield Zero yield! Or' (And' data clock) (And' (dff data clock) (Not' clock)) }
问题根源
FsUnit的should equal断言依赖对象的默认相等性规则。对于seq<'T>(即.NET的IEnumerable<T>)来说,默认的Equals实现是引用相等判定——只要两个序列是不同的实例,哪怕它们包含的元素完全一致,也会被判定为不相等。
你的测试中,dff返回的是一个全新的序列实例,而seq {Zero}/Seq.singleton Zero是另一个独立实例,所以equal断言会失败,哪怕元素看起来完全一致。
而转成列表后测试能通过,是因为F#的List<'T>类型重写了Equals方法,实现的是值相等判定——会逐个遍历元素对比内容,而非比较对象引用。
解决办法
使用FsUnit专门为序列相等性提供的equalSeq断言,它会自动遍历两个序列的所有元素,逐个验证内容是否一致:
修改后的测试代码:
[<Fact>] let ``Initialization of DFF`` () = dff Seq.empty Seq.empty |> should equalSeq (seq {Zero})
这样无需转换类型,就能直接完成序列的内容相等性验证。
内容的提问来源于stack exchange,提问作者bmitc

