我们将要测试的函数是广义(generalized,是百度翻译的,不知道到底是什么意思)的版本,输出多字组成的名字;不同的是,函数结果返回名字,而不是打印。结果是有两个列表的元组:一个包含多字的名字,另一个包含单个词的名字。在函数的术语中,这个操作称为分区(partitioning),我们使用来自标准的F# 库中的List.partition 函数,很容易就能实现需要的函数:
> let partitionMultiWord(names:list<string>)=
names|> List.partition (fun name -> name.Contains(" "));;
val partitionMultiWord : string list -> stringlist * string list
partition 函数的一个参数为判断条件(predicate),把输入列表分成两个列表,第一个列表包含条件为 true 的元素,第二个包含剩余的元素。清单11.10 是为前面声明的函数而写的两个单元测试。
清单11.10 分区操作的单元测试(F#)
module PartitionTests =
[<Fact>]
letpartitionKeepLength() =
lettest = ["A"; "A B"; "A B C"; "B" ]
letmulti, single = partitionMultiWord(test)
Assert.True(multi.Length+ single.Length = test.Length) [1]<-- 验证返回列表的长度
[<Fact>]
letpartitionNonEmpty() =
lettest = ["Seattle"; "New York"; "Reading"]
letexpected = ["New York"], ["Seattle"; "Reading"]
Assert.Equal(expected,partitionMultiWord(test)) <-- 用结构相等测试结果
清单11.10 显示了两个单元测试,实现的函数用Fact 特性值标志。第一个测试[1]检查结果返回两个列表的长度之和,与原始列表的长度相同。这个简单的方法能够验证经过分区以后,各部分没有丢失任何元素。在这个清单中,只有一个输入(值test),我们也可以简单地扩展这个测试,使用多个输入列表。
第二个测试更有意义,因为使用了我们前面讨论的结构相等特征。它声明了两个值,一个是测试的输入值,另一个表示测试输出的期望值。expected 是元组值,有两个列表,第一个列表只包含一个元素,是唯一的由多字组成的名字,第二个列表包含一个字的名字,顺序与在输入列表中出现的相同。如果运行测试,判断[2]成功,因为运行时使用结构相等,比较元组,和元组中包含的列表。实际上,是比较列表中所有单独的字符串。
正如我们所看到的,结构相等是简单而重要的功能,简化单元测试,虽然它不是函数式编程的基本内容。帮助测试的更重要、更本质的函数技术,是函数组合(function composition)。