Midje和Speclj有什么区别?

10

两者都看起来还不错。我想了解每个库特别擅长或缺乏的地方,特别是在Web应用程序测试方面。

5个回答

17

我没有使用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的移植。它也试图成为一个通用工具,但它是半自由意志软件。正如亚伯拉罕·林肯所说:“那些喜欢这种事情的人会发现这就是他们喜欢的事情。”


8
Midje最大的好处是它提供了专注于测试事物的抽象,而不需要测试它们的所有组成部分,这些部分通常会拖累整个世界。
如果您有一个涉及调用辅助函数以生成时间戳、将某些内容放入数据库或消息队列、进行API请求、缓存某些内容、记录某些内容等的函数,则希望知道这些涉及到整个世界的函数调用已发生(有时还要知道它们发生了多少次),但实际执行它们与您正在测试的函数无关,而被调用的函数通常应该有自己的单元测试。
假设您的代码中有以下内容:

(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的快速预览,但展示了它擅长的本质。您可以看到,几乎不需要额外的复杂度来表达:
  1. 函数应该产生什么(尽管每次调用函数时timestamp函数产生的内容都不同),
  2. 已调用timestamp函数和logging函数,
  3. 已仅调用一次logging函数,
  4. logging函数收到了预期的第一个参数,
  5. 您不在意收到的第二个参数是什么。
我想通过这个例子表达的主要观点是,在简单的部分中表达复杂代码的测试,而不是试图一次性测试所有内容,这是一种非常清洁且紧凑的方式,而将所有内容一次性测试则应该在集成测试中使用。

我必须承认我的观点有偏见,因为我积极使用midje,而我只是看过speclj。但我的感觉是,speclj可能更受那些使用类似Ruby库并发现这种测试思维方式理想的人的欢迎,基于这种经验。这是选择测试框架的完全合理的理由,也可能有其他好的方面,希望其他人能够评论。


8
我肯定会选择Speclj
Speclj很容易集成和使用。它的语法比Midje更为简洁。Speclj基于RSpec,为您提供了所有Ruby程序员习惯使用的舒适性,同时保留了Clojure的特殊性。
而且Speclj中的自动运行器非常棒。
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))))))

这种简单的模拟方法非常直接,没有任何误导。
至于测试Web应用程序,Speclj表现良好。实际上,Speclj支持已经构建到Joodo中。
免责声明:我编写了Speclj。

1
对于大多数情况,使用with-redefs方法效果很好,但如果您使用的是1.2版本并且不想将所有绑定更改为with-redefs,则可以考虑其他方法...... Midje的一个好处是它在所有Clojure版本(自1.2以来)上都能无缝运行... 诚然,这只能归类为“好玩”,而不是“重要”。免责声明:我是Midje的贡献者。 :) - Alex Baranosky

4
我会说,Midje 特别擅长创建用于表达存根和模拟的 DSL。如果您关心存根和模拟,并且想经常使用它,我会选择 Midje 而不是 Speclj,因为它具有表示这些类型测试的抽象,比 slagyr 在他的答案中提供的方法更加简洁。
另一个选择是,如果您想要一种更轻量级的方法,则可以使用 Conjure 存根/模拟库,该库旨在与 clojure.test 一起使用。
Speclj 的优点在于非常像 RSpec,包含“describe”和“it”... Midje 实际上也可以支持嵌套事实,但不如 Speclj 那样优雅。
免责声明:我是 Midje 和 Conjure 的贡献者。 :)

1
我建议使用Midje而不是Speclj。对于Speclj,我认为它对模拟的支持不太好,文档与Midje相比也显得稀少。
此外,Midje的语法更好:
(foo :bar) => :result compared to (should= (foo :bar) :result)

2
我不确定midje语法是否客观上更好。我并不特别喜欢伪中缀风格。 - Rayne

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