11.2.3 测试组合函数

728 篇文章 1 订阅
349 篇文章 0 订阅

11.2.3 测试组合函数

 

在第11.1.2 节,我们讨论跟踪代码中的依赖关系时,使用的C# 方法,类似于上两个示例中的F# 函数,演示函数式编程使得更容易识别函数做什么,访问什么数据。这不仅在写代码时非常有用,而且在测试时也极其有用。

在第11.1 节,我们写过一个命令式方法,打印出由多字组成的名字,但是,它有副作用,会从作为参数传递进来的可变列表中删除元素。只要我们以后不再使用这个列表,就不会引起任何问题。对这个方法的任何单元测试以检查打印输出,都会成功。

使这个方法棘手的是,如果我们把它与其他同样正确的方法连一起使用时,可能会得到意想不到的结果,因此,想彻底测试命令代码很难。原则上,我们应该测试的是,每个方法只做它应该做的,且仅此。不幸的是,“且仅此”部分真的难以测试,因为任何一段代码都可以访问并修改共享的可变状态的任何一部分。

而在函数式编程中,我们不会修改任何共享状态,因此,我们只需要验证,对于所有给定的输入,函数返回正确的结果。这也说明,当我们把两个测试过的函数放在一起使用时,只要测试这个组合有相应的结果:并不需要验证其中的函数不会以微妙的方式破坏了对方的数据。清单11.11 显示的这种测试,看起来完全没有意义,但是想象一下,如果我们使用List<T>,而不是不可变的F# 列表,会是什么样子。

 

清单11.11 测试两个有副作用的函数(F#)

[<Fact>]

let partitionThenLongest() =

  lettest = ["Seattle"; "New York"; "Grantchester"]

  letexpected = ["New York"], ["Seattle"; "Grantchester"]

 

  letactualPartition = partitionMultiWord(test)  | [1]

  letactualLongest = getLongest(test)          |

 

  Assert.Equal(expected,actualPartition)      | [2]

  Assert.Equal("Grantchester",actualLongest)  |

 

可以发现,单元测试顺序地运行两个函数[1],但只使用部分结果,验证结果是合我们的期望值[2]。这样,函数调用是独立的,如果它们不包含任何副作用,我们可以自由地改变调用的顺序。在函数世界中,这个单元测试根本不需要:我们已经为每个单独的函数写过单元测试,而这个测试并没有验证任何额外的行为。

然而,如果我们想要使用可变的List<T> 类型,写类似的代码,这个测试可能会捕获到我们在第11.1 节发现过的错误。如果partitionMultiWord 函数修改了值 test 所引用的列表,比如,删除所有单字的名字,那么,第二次调用的结果就不可能是测试预期的“Grantchester”。这是关于函数式代码重要的一般观点:如果我们很好地测试了所有的基本部分,再测试组合的代码,那么,我们就不需要在新的配置中,测试这些基本部分是否仍然有正确的行为。

到目前为止,我们已经讨论了函数式程序的重构和测试。我们发现,一等函数能够减少代码重复,不可变的数据结构有助于我们了解代码在做什么,而且,减少了测试两段代码可能会互相干扰的需要。

本章的剩余部分将会讨论如果代码被执行时,如何能够利用这一优势,使代码更高效。首先,我们需要了解何时有一定的灵活性,F# 和C# 如何决定何时执行代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值