两者都看起来还不错。我想了解每个库特别擅长或缺乏的地方,特别是在Web应用程序测试方面。
我没有使用speclj,我是Midje的第一个作者。其他人没有提到的一点是,Midje试图利用函数式和面向对象语言之间的差异。
其中一个区别是不可变性。因为大多数函数只依赖于它们的输入,而不依赖于包含的状态,所以你对它们所做的真相陈述在感觉上与面向对象测试的不同。在面向对象测试中,您可以创建以下示例:"给定这个历史和这些输入,该方法会产生这样和那样的结果"。
在函数式语言中,看起来示例只是更简单的:"给定这些输入,此函数返回这样和那样"。但我认为这并不完全正确。我认为系统中的其他函数扮演了类似于状态/历史的角色:它们是您想要在智力上掌握的东西之一。函数及其关系是您希望测试帮助您清晰地思考的事物。
出于这个原因,Midje的写作基于这样的假设:一个良好的开发过程包括:
然后,按照典型的 Mockist 风格,您从大致自上而下或从外到内开发,并允许在从错误中恢复或有更好的想法时进行必要的迭代。
最终结果是一堆函数,由测试或(如Midje所称)关于函数及其依赖项的“事实”记录它们之间的互动关系。一些人评论说,Midje给人一种Prolog/逻辑编程的感觉,这不是偶然的。像往常一样,测试是示例,但 Midje试图使它们看起来更像真相陈述。这就是它唯一实际创新特征、元常量的理由。下面是它们的一个示例:
(fact "right changes the direction, but not the position"
(right (snapshot north ...position...)) => (snapshot west ...position...)
(right (snapshot east ...position...)) => (snapshot north ...position...)
(right (snapshot south ...position...)) => (snapshot east ...position...)
(right (snapshot west ...position...)) => (snapshot south ...position...))
在这种情况下,实际位置与函数right
的真实情况无关,除了它永远不会改变。元常量的概念是,它是一个关于哪些没有知识的值,除了在测试中明确说明的内容。在测试中,往往很难确定什么是本质的,什么是偶然的。这会带来许多不良影响:如理解和可维护性等。元常量提供了清晰度。如果某个值是包含键:a
和值为3
的映射或记录,那么需要明确地声明它是否重要。(fact
(full-name ..person..) => "Brian Marick"
(provided
..person.. =contains=> {:given-name "Brian", :family-name "Marick"}))
这个测试明确了人们关注的重点,同时也明确了什么不重要(除了两个名称之外)。
用数学术语来说,Midje试图让您发表像“对于所有x,其中x...”这样的陈述,同时仍然是一个测试工具,而不是定理证明器。
这种方法受到了“伦敦式”模拟重度TDD的启发,这种TDD在 Growing Object-Oriented Software中有所描述,这也是我通常在编写Ruby代码时使用的方法。但事实证明,它的感觉相当不同,很难用言语来描述。但是,这种感觉需要比只有with-redefs
更多的工具支持。
总之,Midje部分是为了寻找一种功能性TDD风格,而不仅仅是OO TDD的移植。它也试图成为一个通用工具,但它是半自由意志软件。正如亚伯拉罕·林肯所说:“那些喜欢这种事情的人会发现这就是他们喜欢的事情。”
(defn timestamp [] (System/currentTimeMillis))
(defn important-message [x y] (log/warnf "Really important message about %s." x))
(defn contrived [x & y]
(important-message x y)
{:x x :timestamp (timestamp)})
以下是使用Midje进行测试的方法:
(ns foo.core-test
(:require [midje.sweet :refer :all]
[foo.core :as base]))
(fact
(base/contrived 100) => {:x 100 :timestamp 1350526304739}
(provided (base/timestamp) => 1350526304739
(base/important-message 100 irrelevant) => anything :times 1))
我必须承认我的观点有偏见,因为我积极使用midje,而我只是看过speclj。但我的感觉是,speclj可能更受那些使用类似Ruby库并发现这种测试思维方式理想的人的欢迎,基于这种经验。这是选择测试框架的完全合理的理由,也可能有其他好的方面,希望其他人能够评论。
lein spec -a
一旦您使用了这个工具一段时间,您会想知道在手动运行测试时,您是如何完成工作的。
模拟不是问题,因为您可以简单地使用 with-redefs。@rplevy 在 Speclj 中的示例看起来像这样。
(ns foo.core-spec
(:require [speclj.core :refer :all ]
[foo.core :as base]))
(describe "Core"
(it "contrives 100"
(let [message-params (atom nil)]
(with-redefs [base/timestamp (fn [] 1350526304739)
base/important-message #(reset! message-params [%1 %2])]
(should= {:x 100 :timestamp 1350526304739} (base/contrived 100))
(should= 100 (first @message-params))))))
(foo :bar) => :result compared to (should= (foo :bar) :result)