F#开发和单元测试?

108

我刚开始学F#,这是我接触的第一种函数式语言。我之前基本上都在用C#,现在喜欢F#能让我重新审视编写代码的方式。但有一个方面我感到有点迷惑,那就是编写代码的过程发生了变化。在C#中,我已经使用TDD多年了,非常喜欢通过单元测试来掌握自己所处的位置。

到目前为止,我的F#编程过程是编写一些函数,在交互式控制台中玩弄它们,直到我“合理地”确信它们可行,并进行调整和组合。这对于像欧拉计划这样的小规模问题很有效,但我无法想象通过这种方式构建大型项目。

人们如何为F#程序创建单元测试套件?是否有类似TDD的东西?任何指针或想法都受到赞赏。


1
http://www.expert-fsharp.com/CodeSamples/Forms/AllItems.aspx?RootFolder=%2fCodeSamples%2fChapter18%2fExample06 展示了一个使用NUnit和F#的简单示例。 - itowlson
请参见https://dev59.com/8HM_5IYBdhLWcg3wPAZF,了解有关如何处理F#中的单元测试的信息。 - Mauricio Scheffer
相关:https://dev59.com/Km035IYBdhLWcg3wBLXb#5669263(取消引用不仅仅是脚注/评论,因为它在此页面的答案集中) - Ruben Bartelink
这些答案中缺少的一件事是Foq、AutoFixture.AutoFoq和AutoFixture.xUnit的适当示例,以及与F#类型推断相关的内容。请参见http://trelford.com/blog/post/test5.aspx和http://trelford.com/blog/post/fstestlang.aspx了解一些信息,有一天我会在这里写一个适当的答案。 - Ruben Bartelink
7个回答

77
像F#这样的函数式语言非常适合测试驱动开发者:小的函数能够给出可重复确定性结果,完美地配合单元测试。此外,F#语言中还有一些用于编写测试的能力。例如,对象表达。你可以很容易地为那些以接口类型作为输入的函数编写虚假实现。
如果说什么的话,F#是一个一流的面向对象语言,你可以使用与C#进行TDD时相同的工具和技巧。此外,也有一些特别为F#编写或编写在F#上的测试工具: Matthew Podwysocki在一个系列文章中写了很棒的关于函数式语言单元测试的内容。Uncle Bob也在一篇思考性的文章这里讨论了该主题。

9
我还开发了(并正在积极开发)一个专门针对F#的单元测试库,名为Unquote:http://code.google.com/p/unquote/。它使用F#引用将测试断言编写为普通的静态检查F#布尔表达式,并自动生成漂亮的测试失败消息。它与特定于xUnit.net和NUnit的无配置工作,并通常支持任何基于异常的单元测试框架。它甚至可以在FSI会话中工作,允许从交互式测试无缝迁移到正式测试套件。 - Stephen Swensen
还有Pex,不过那个可能比较难理解。 - Benjol
1
Uncle Bob的链接似乎失效了。 - Aage
2020年更新:为了更加详尽,应该将Expecto https://github.com/haf/expecto/添加到列表中(请参见作者下面的帖子)。在我看来,这是最适合F#的FP风格测试框架。 - Digital Stoic

22

我使用 NUnit,它对我来说阅读起来并不困难,写起来也不费力:

open NUnit.Framework

[<TestFixture>]
type myFixture() = class

    [<Test>]
    member self.myTest() =
       //test code

end

因为我的代码是由F#和其他.Net语言混合编写而成的,所以我很喜欢以基本相同的方式和类似的语法在F#和C#中编写单元测试。


4
在阅读了其他回答之后,我尝试使用了FSUnit,并认为它非常好用。它与TestDriven.Net(以及NUnit)配合得很好,鼓励以流畅的风格编写自文档化的测试,而且正如Ray所说,“更适用于F#语言”。这对于仅有21行代码(和一些布局/命名建议)来说并不错!两个快速提示:
  1. 预编译的FSUnit DLL 对我无效。从源代码构建(FsUnit.NUnit-0.9.0.fs)解决了这个问题。
  2. TestDriven.Net 不能识别看起来像 `这样` 的 TextFixture 名称。使用双引号形式的测试名称可以被识别。
- David Glaubman

14

看一下 FsCheck,它是一个自动化测试工具,适用于 F#,基本上是 Haskell 的 QuickCheck 的移植版。它允许您提供程序的规范,以函数或方法应满足的属性形式,并在大量随机生成的情况下测试 FsCheck 是否满足这些属性。

FsCheck CodePlex 页面

FsCheck 作者页面


是的,我认为FsCheck提供了比传统单元测试框架(如NUnit等)更多的功能。 - Robert

11

如dglaubman所建议,您可以使用NUnit。 xUnit.net也提供了对此的支持,并且能够与TestDriven.net良好协作。代码看起来类似于NUnit测试,但不需要将测试包装在包含类型中。

#light

// Supply a module name here not a combination of module and namespace, otherwise
// F# cannot resolve individual tests nfrom the UI.
module NBody.DomainModel.FSharp.Tests

open System
open Xunit

open Internal

[<Fact>]
let CreateOctantBoundaryReordersMinMax() =
    let Max = VectorFloat(1.0, 1.0, 1.0)
    let Min = VectorFloat(-1.0, -1.0, -1.0)

    let result = OctantBoundary.create Min Max

    Assert.Equal(Min, result.Min)     
    Assert.Equal(Max, result.Max) 

从1.9.1版本开始,Xunit的新重载似乎对我的F#代码造成了混乱。 - Rick Minerich
@RickMinerich 我也遇到了同样的问题。最终我只好添加显式类型注释,以便选择正确的重载。然而,这确实会在你的代码中增加更多的噪音。 - Erik Schierboom

11

我认为这是一个非常有趣的问题,我自己也一直在思考。目前为止,我的想法只是思考,所以请根据它们的本质来看待。

我认为自动化测试套件的安全网太有价值了,不能轻易放弃,即使交互式控制台可能很诱人,所以我计划继续像以往一样编写单元测试。

.NET 的主要优势之一是跨语言能力。我知道我很快就要编写 F# 生产代码了,但我的计划是使用 C# 编写单元测试,以便我顺利进入对我来说是新语言的领域。通过这种方式,我还可以测试我在 F# 中编写的内容是否与 C#(以及其他 .NET 语言)兼容。

采用这种方法,我理解 F# 具有某些特定的功能,我只能在 F# 代码内部使用,而不能作为公共 API 的一部分暴露出去,但我会接受这一点,就像我今天接受 C# 允许我表达某些东西(如 uint),而不符合 CLS 标准,因此我不使用它们一样。


2
你的计划进展如何?使用c#代码测试f#代码容易吗?我开始学习f#,我的计划是用f#编写项目的某些部分,并且我有同样的想法:为f#编写的单元测试也用c#。 - Peter Porfy
@Mark 有更新吗?我也很难使用F#进行TDD流程。 - Scott Nimrod
1
@ScottNimrod 最近有不少更新:我在Pluralsight上发布了四门关于使用F#进行测试或TDD的课程。你还可以在我的Lanyrd个人资料中找到许多免费的会议演讲录音。最后,欢迎来我的博客逛逛。 - Mark Seemann
3
@ScottNimrod 我强烈建议你花时间和/或金钱观看完整的Mark PS课程系列-这将以最大效率将所有内容组合在你的脑海中。虽然它可能与你的具体需求有关,但“用F#实现功能架构”也可以连接很多点,也应该被认真考虑。 - Ruben Bartelink

7

你可以看一下 FSUnit - 虽然我还没有用过它,但值得一试。肯定比在F#中使用(原生的)NUnit更好。


1
ShdNx,为什么你不推荐使用NUnit?Don Syme的F#书籍中介绍了NUnit进行测试,看起来与在C#中使用NUnit非常相似。FSUnit DSL看起来很酷,但对于已经熟悉NUnit(Mathias已经“使用TDD多年”)的人来说,你有没有经验表明在F#中使用NUnit比在C#或VB中更具问题性? - itowlson
我赞同itowlson的评论和问题。毫无疑问,在F#中使用NUnit看起来相当奇怪,但除此之外,您是否知道特定的问题使使用其他工具成为一个好主意? - Mathias
1
我认为“看起来很奇怪”通常是寻找更好的东西的有力理由。看起来奇怪意味着难以阅读,而难以阅读意味着存在漏洞。 (我假设“看起来奇怪”与“看起来新和/或不熟悉”完全不同-不熟悉会变得熟悉,而奇怪则会保持奇怪。) - James Moore
1
说实话(正如我在回答中提到的),我还没有使用过FSUnit,但我已经读到了在F#中使用NUnit非常痛苦。如果我的说法不正确,请见谅。 - ShdNx
4
请注意,如果您使用FsUnit,您仍将拥有TestFixtures和Test成员。您不会拥有的是标准的Assert.X调用。FsUnit只是为NUnit的这部分提供了一个包装器,使它更适合F#语言。 - Ray Vernagus

2
尽管有些晚,我还是要欢迎Mathias加入F#(迟到总比不来好;)),并且提醒你可能会喜欢我的单元测试库Expecto。
Expecto有一些你可能会喜欢的功能:
- 基于F#语法,将测试作为值;使用纯F#编写生成测试。 - 使用内置Expect模块或外部库如Unquote进行断言。 - 默认并行测试。 - 测试你的Hopac代码或异步代码;Expecto在整个过程中都是异步的。 - 通过Logary Facade插件化日志记录和度量;轻松编写适配器用于构建系统,或使用计时机制构建InfluxDB + Grafana仪表板以查看测试执行时间。 - 内置支持BenchmarkDotNet。 - 内置支持FsCheck;使得使用生成/随机数据构建测试或构建对象/actor状态空间的不变模型变得容易。
--
open Expecto

let tests =
  test "A simple test" {
    let subject = "Hello World"
    Expect.equal subject "Hello World" "The strings should equal"
  }

[<EntryPoint>]
let main args =
  runTestsWithArgs defaultConfig args tests

https://github.com/haf/expecto/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接