使用clojure.spec、clojure/test.check、RDD和TDD生成宾果卡

2020-10-31 02:39:16

Clojure开发人员巴塞罗那已经运行了几年。因为我们人数还不多,所以我们通常会参加暴徒编程课程,作为我们所说的传奇故事的一部分。对于每个传奇,我们选择一个练习或形式法,并在最初的一到两次会议中解决它。之后,我们开始想象使用不同的Clojure/ClojureScript库或技术的练习变体,我们希望在接下来的会话中探索和开发这些变体。一旦我们觉得我们无法想象更有趣的变化,或者我们厌倦了一个特定的问题,我们就会选择一个不同的问题来开始一个新的传奇。你应该试试做传奇,它们很有趣!最近,我们一直在研究宾果型。这些是我们为检查随机生成的宾果卡而编写的测试:我们最初编写的生成它们的代码类似于(我们没有保存原始的代码):正如您可以看到的那样,测试并不关心宾果卡的每一列中包含哪些特定的数值。他们只是在检查他们是否符合宾果卡的规格。这使得它们非常适合基于属性的测试。在接下来的宾果传奇会议中,我建议使用clojure.spec创建宾果卡。SPEC是一个Clojure库,用于描述数据和函数的结构。规范可用于验证数据、符合(分解)数据、解释无效数据、生成符合规范的示例,以及自动使用生成性测试来测试功能。

有关这个奇妙的库的简要介绍,请参阅Arne Brasseur对clojure.spec talk的介绍。我以前在工作中用过clojure.spec。在我目前的客户Green Power Monitor,我们已经使用它来验证流经一些键名称空间的一些重要公共函数的数据的形状(在某些情况下,还有一些类型)。我们开始使用前置条件和后置条件进行验证(请参阅Fogus的:Pre和:POST以了解更多信息),从那时起,开始使用clojure.spec编写其中一些条件是很自然的一步。Spec规范的另一个常见用途是生成符合规范的随机数据,用于基于属性的测试。在Bingo Kata案例中,我认为我们可以在生产代码中使用这种随机生成符合规范的数据的功能。这意味着,我们可以使用clojure.spec描述宾果卡,然后利用该规范使用clojure.test.check的生成函数随机生成宾果卡,而不是编写代码来随机生成宾果卡,然后测试结果是否符合预期。带着这个想法,我们开始一点一点地为REPL上的Bingo列创建一个规范(为简洁起见,这里您可以看到的是规范的最终形式):然后我们发现了clojure.spec的coll-of函数,它使我们能够稍微简化规范:一旦我们认为我们拥有了它,我们就尝试使用列规范来使用clojure.test.check的Generate函数来生成列,但是我们得到了以下错误:当然,我们是在尝试大海捞针……。在对REPL进行了一些试错并阅读了clojure.spec指南之后,我们找到了clojure.spec的int-in函数,并最终设法生成了宾果列:然后,我们使用REPL中的规范代码来编写宾果卡规范:在该规范中,我们编写了create-column-spec工厂函数,该函数创建列规范以消除不同列的规范之间的重复。有了这一点,就可以在一行代码中创建宾果卡:基于属性的测试根据输入做出关于代码输出的语句,并且这些语句针对许多不同的可能输入进行验证。Jessica Kerr(基于属性的测试:这是什么?)。

有了这些规范,只需使用clojure.spec创建的生成器,就可以很容易地将我们的宾果卡测试更改为使用基于属性的测试,而不是基于示例的测试:在代码中可以看到,我们重用了为基于示例的测试编写的check-column函数。这一变化是如此容易,因为:。正如我前面提到的,最初的示例测试已经检查了有效宾果卡的属性。这意味着他们并不关心宾果卡的每一列都包含了哪些特定的数值,相反,他们只是检查这些卡是否遵循宾果卡是否有效的规则。

Kata的下一个用户故事要求我们检查宾果牌,看看它的玩家是否赢了。我们认为这可能很容易实现,因为我们只需要检查卡片中的号码是否包含在被叫号码集中,所以我们不做TDD,而是在REPL上做了一点REPL驱动的开发(RDD):一旦我们让实现工作了,我们就把它从REPL复制到它相应的名称空间中