4

我正在使用 NUnit 在 Visual Studio 2013 中使用 Resharper 的测试运行程序对 F# 代码进行单元测试。我在返回这些类型实例的项目和函数中有几个 F# 记录和区分联合类型,例如:

type MyRecord = { A : int; B : int }
let f () = { A = 4; B = 7 }

现在,当我编写如下测试用例(使用 FSUnit)时:

[<Test>] let ``test that always fails`` () = f () |> should be { A = 77; B = 99 }

我收到一条非常无用的输出消息,类似于“预期:MyNamespace.MyRecord 但发现:MyNamespace.MyRecord”。

问题当然是 N​​Unit 显然用于object.ToString()生成输出消息,而我希望使用它sprintf "%A" object来生成更有用的输出消息,例如“预期:{ A = 77; B = 99} 但发现:{ A = 4; B = 7}”。

我知道我可以覆盖ToString所有记录和区分联合类型,如下所示:

type MyRecord = { A : int; B : int } with override this.ToString () = sprintf "%A" this

但是,这会使类型定义变得混乱,其中包含仅用于测试目的的代码,并且在将此代码复制并粘贴到所有类型定义时有点违反不要重复自己的原则。另请注意,在 F# 3.1 中不推荐在类型扩充中添加覆盖成员。

老问题

所以,问题是:有没有办法在不显式覆盖的情况下为失败的 NUnit 测试获取结构化输出ToString?NUnit 有一些插件机制吗?我检查了 NUnit 的来源,似乎没有明显或受支持的方式来做到这一点。解决方案不必很快——毕竟,代码只会在失败的单元测试中执行。但是,它应该是通用的,并且适用于将来添加的所有 F# 记录和可区分联合类型,而无需更改任何代码。谢谢。

新问题

正如 Mark Seemann 在评论中指出的那样,Unquote 库是一种不存在非结构化输出问题的替代方案。所以我将用 Unquote 替换 FSUnit。但是,Unquote 还存在另一个问题:它使用 F# 的反射库来评估和减少可区分联合。显然,反射无法处理另一个库中定义的内部类型,导致所有单元测试都失败并出现异常。解决方法是公开所有类型,显然这并不理想。

所以我的设置是:一个包含应该测试的内部类型的库;该库使用该InternalsVisibileTo属性使其内部对测试程序集可见。执行测试时,Unquote 使用 F# 的反射,这反过来似乎无法正确反映内部类型。

还有其他已知的解决方法吗?

4

1 回答 1

4

你可以覆盖 NUnit 提供的消息:

let shouldEqual (x: 'T) (y: 'T) = 
    Assert.AreEqual(x, y, sprintf "Expected: %A\nActual: %A" x y)

sprintf "%A"就 F# 而言,它将运行良好。

还有一个理由更喜欢这个版本而不是should equal. 这是类型安全的,不会导致对泛型值的神秘测试失败(请参阅FsUnit `should equal` failed on `Some []` and Weird None behavior in type providers

于 2014-06-11T09:25:19.353 回答