我正在使用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 }
我得到了一条非常有用的输出消息:“Expected:MyNamespace.MyRecord但是找到了:MyNamespace.MyRecord”。
问题当然是NUnit显然使用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中不推荐在类型扩充中添加覆盖成员。
旧问题
所以,问题是:有没有办法在没有明确覆盖ToString
的情况下获得NUnit测试失败的结构化输出? NUnit有一些插件机制吗?我已经检查了NUnit的来源,似乎没有明显或受支持的方式。解决方案不一定非常快 - 毕竟,代码只能在失败的单元测试中执行。但是,它应该是通用的,并且适用于将来添加的所有F#记录和区别联合类型,而不需要任何代码更改。感谢。
新问题
正如Mark Seemann在评论中指出的那样,Unquote库是一种替代方案,它没有非结构化输出的问题。所以我将用Unquote代替FSUnit。但是,Unquote还有另外一个问题:它使用F#的反射库来评估和减少有区别的联合。显然,反射不能处理另一个库中定义的内部类型,导致所有单元测试失败并出现异常。解决方法是将所有类型公开,这显然不是理想的。
所以我的设置是:一个包含应该测试的内部类型的库;该库使用InternalsVisibileTo
属性使其内部对测试程序集可见。执行测试时,Unquote使用F#的反射,而反射似乎无法正确反映内部类型。
还有其他已知的解决方法吗?
答案 0 :(得分: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` fails on `Some []`和Weird None behaviour in type providers)