ITADN
clojure-expectations/clojure-test · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

clojure.test 用于 Expectations Open in Gitpod

一个与 clojure.test 兼容的 经典 Expectations 测试库] 版本。

在哪里?

Clojars cljdoc badge Slack Join Slack

试一试:

clj -Sdeps '{:deps {com.github.seancorfield/expectations {:mvn/version "RELEASE"}}}'

是什么?

该库提供了一种比 clojure.test 更具表达力的测试编写方式, 同时仍与 clojure.test 及其所有工具完全兼容。

虽然 clojure.test 使用 (is (= ... ...))(is (thrown? ... ...)) 提供基本断言,但 Expectations 还支持谓词、 正则表达式、Specs 以及基于集合的测试。

你可以在测试中“混合搭配” clojure.testexpectations.clojure.test 的功能,使用来自 clojure.testdeftest 或来自 本库的 defexpect 来包装你的测试,或者你也可以单独使用 expectations.clojure.test, 因为它提供了与所有顶层 clojure.test 函数和宏(用于处理 fixtures 和运行测试)等效的功能。

以下代码是等效的:

(deftest my-test-1
  (is (= 2 (+ 1 1)))
  (is (thrown? ArithmeticException (/ 1 0))))

(defexpect my-test-2
  (expect 2 (+ 1 1))
  (expect ArithmeticException (/ 1 0)))

但您也可以执行以下操作:

(defexpect my-test-3
  (expect even? (+ 1 1))
  (expect #"foo" "It's foobar!")
  (expect ::adult-age 42)) ; ::adult-age is a Spec

请参阅下方的 REPL 会话示例以获取更多详细信息。

该库除了 clojure.test 本身外没有其他依赖项,并且 应该与编辑器中以及命令行工具中所有现有的基于 clojure.test 的工具兼容。

适用于 Clojure 1.9 及更高版本。

适用于自托管的 ClojureScript(具体而言, planck)。 请参阅 Getting Started with ClojureScript 以获取详细信息。

我认为 Expectations 已经成熟且稳定,但在此阶段处于某种 维护模式。 我已经开始将我的几个项目迁移到 LazyTest,它正在积极维护 并提供更好的报告以及更具表达力的测试 DSL。 LazyTest 具有 一个 lazytest.extensions.expectations 命名空间,为 Clojure 上的 Expectations 提供了相当完整的替代方案 (不支持 ClojureScript),因此从 Expectations 迁移到 LazyTest 相当容易。 请注意,LazyTest 与 clojure.test 工具兼容——它有自己的测试运行器和报告——这可能会影响您的 决定,特别是如果您依赖 Cursive 的内置测试运行器(或 Calva 的——尽管自定义 REPL 片段可以缓解这一点,至少对我来说是这样)。

示例 REPL 会话

以下是一个示例 REPL 会话,展示了该库提供的一些功能。 有关更详细的文档,请从 Getting Started 开始,并逐步阅读其中列出的各个部分。

(ns my.cool.project-test
  (:require [clojure.spec.alpha :as s]
            [clojure.test :refer [deftest is]]
            [expectations.clojure.test
             :refer [defexpect expect expecting
                     approximately between between' functionally
                     side-effects]]))

;; mix'n'match libraries:

(deftest mixed
  (is (= 2 (+ 1 1)))
  (expect even? (+ 1 1)))

;; simple equality tests:

(defexpect equality
  (expect 1 (* 1 1))
  (expect "foo" (str "f" "oo")))

;; the expected outcome can be a regular expression:

(defexpect regex-1
  (expect #"foo" "It's foobar!"))

;; the expected outcome can be an exception type:

(defexpect divide-by-zero
  (expect ArithmeticException (/ 12 0)))

;; the expected outcome can be a predicate:

(defexpect no-elements
  (expect empty? (list)))

;; the expected outcome can be a type:

(defexpect named
  (expect String (name :foo)))

;; the expected outcome can be a Spec:

(s/def ::value (s/and pos-int? #(< % 100)))
(defexpect small-value
  (expect ::value (* 13 4)))

;; if the actual value is a collection, the expected outcome can be an element or subset "in" that collection:

(defexpect collections
  (expect {:foo 1} (in {:foo 1 :cat 4}))
  (expect :foo (in #{:foo :bar}))
  (expect :foo (in [:bar :foo])))

;; just like clojure.test's testing macro to label groups of tests
;; you can use expecting to label groups of expectations (this uses
;; some of more advanced features listed below):

(defexpect grouped-behavior
  (expecting "numeric behavior"
    (expect (more-of {:keys [a b]}
                     even? a
                     odd?  b)
            {:a (* 2 13) :b (* 3 13)})
    (expect pos? (* -3 -5)))
  (expecting "string behavior"
    (expect (more #"foo" "foobar" #(clojure.string/starts-with? % "f"))
            (str "f" "oobar"))
    (expect #"foo"
            (from-each [s ["l" "d" "bar"]]
              (str "foo" s)))))

deftest 类似,defexpect 宏会创建一个包含测试的函数。你可以单独运行每个函数:

user=> (equality)
nil

如果测试通过,则不会输出任何内容,并返回 nil。让我们来看一个失败的测试:

user=> (defexpect inequality (expect (* 2 21) (+ 13 13 13)))
#'user/inequality
user=> (inequality)

FAIL in (inequality) (.../README.md:117)
expected: (=? (* 2 21) (+ 13 13 13))
  actual: (not (=? 42 39))
nil

输出由 clojure.test 的标准报告功能生成。 =? 运算符是 clojure.testassert-expr 多态方法的扩展, 允许进行 Expectations 风格的谓词或相等性测试(基于 “expected” 表达式解析为函数还是其他值):

user=> (defexpect not-at-all-odd (expect odd? (+ 1 1)))
#'user/not-at-all-odd
user=> (not-at-all-odd)

FAIL in (not-at-all-odd) (.../README.md:133)
expected: (=? odd? (+ 1 1))
  actual: (not (odd? 2))
nil

在这里,我们看到谓词 (odd?) 被应用于来自 clojure.test 的“实际”结果。

is 宏类似,expect 可以将可选的失败消息作为第三个参数:

user=> (defexpect failure-msg
         (expect even? (+ 1 1 1) "It's uneven!"))
#'user/failure-msg
user=> (failure-msg)

FAIL in (failure-msg) (.../README.md:149)
It's uneven!
expected: (=? even? (+ 1 1 1))
  actual: (not (even? 3))
nil

为什么?

TL;DR: 因为我喜欢 "Classic" Expectations 库,但不喜欢必须使用定制的、Expectations 专用的工具。

为什么不直接使用 clojure.test

clojure.test 是一个用于在 Clojure 中编写测试的优秀库。它简单, 内置于语言中,并且得到 Clojure 生态系统的广泛支持。然而,它 只提供基本的断言,并不支持 Expectations 中一些更高级的 测试功能。

为什么不直接使用 "Classic" Expectations 库?

鉴于 Expectations 的简洁性,你可能会想知道为什么 要将你的 Expectations 测试套件迁移到 clojure.test 风格的 命名测试?简短的回答是工具!虽然 Expectations 拥有 针对 Leiningen 和 Boot 的维护良好且稳定的插件,以及一个 Emacs 模式, 但现实是 Clojure 工具链在不断演进,而大多数 这些工具 -- 例如出色的 CIDER, Cursive, Calva (用于 VS Code), 以及 Cognitect 的 test-runner -- 将优先关注 Clojure 内置的测试库。 对于使用未命名测试的原始形式 Expectations, 在 Cursive 中完全不支持,在其他编辑器和工具中也可能存在问题。

围绕 clojure.test 已经发展出一整套 工具生态系统,为了在 Expectations 中利用 这些工具,我们要么需要为每一个工具开发兼容的扩展, 要么需要让 Expectations 与 clojure.test 兼容。

实现这种兼容性的一个主要障碍在于,默认情况下,Expectations 会为测试代码生成“随机”的函数名(函数名基于 expect 主体的文本形式的 哈希码),这意味着每当测试的文本发生变化时,测试 名称也会随之改变。为了解决这个问题,新的 expectations.clojure.test 命名空间通过 defexpect 宏引入了命名期望(模仿 clojure.testdeftest 宏)。虽然这与 Expectations 创建时所秉持的 测试名称 哲学 相悖,但它在工具支持方面为我们带来了巨大的收益!

与“经典”Expectations 的兼容性

expectations.clojure.test 目前支持“经典”Expectations 库中的以下功能:

  • 简单相等性测试
  • 简单谓词测试
  • 规范测试(使用标识规范的关键词)
  • 类测试 -- 参见上文 named
  • 异常测试 -- 参见上文 divide-by-zero
  • 正则表达式测试 -- 参见上文 regex-1regex-2
  • (expect expected-expr (from-each [a values] (actual-expr a)))
  • (expect expected-expr (in actual-expr)) -- 参见上文 collections
  • (expect (more-of destructuring e1 a1 e2 a2 ...) actual-expr)
  • (expect (more-> e1 a1 e2 a2 ...) actual-expr) -- 其中 actual-expr 被传递到每个 a1a2、... 表达式中
  • (expect (more e1 e2 ...) actual-expr)
  • (expect expected-expr (side-effects [fn1 fn2 ...] actual-expr))

阅读 Expectations 文档 以了解这些功能的更多细节。

与“经典”Expectations 的区别

除了为测试提供名称这一明显区别外 -- 这对于 与基于 clojure.test 的工具的兼容性至关重要 -- 以下是其他 需要注意的区别:

  • 你使用基于标准 clojure.test 的工具 -- lein testboot test 以及 Cognitect 的 test-runner -- 而不是 Expectations 专用的工具。
  • 因此,测试在你决定时运行,而不是在 JVM 关闭时运行(这是 Expectations 的默认行为)。
  • 如果你的类路径上有 Paul Stadig 的 Humane Test Output,它将被激活,并且由 =? 报告的失败将与其兼容,从而提供更好的报告。
  • 代替 Expectations 的 in-contextbefore-runafter-run 机制,你可以直接使用 clojure.test 的 fixtures 机制(use-fixtures)。从 2.0.0 版本开始,use-fixtures 通过 expectations.clojure.test 直接暴露,因此你不需要 require clojure.test
  • 代替 Expectations 的“focused”测试概念,你可以使用测试上的元数据,并告诉测试运行器按需“select”测试(例如,Leiningen 的“test selectors”、Boot 的“filters”以及 test-runner-i/-e 选项)。
  • freeze-timeredef-statewarn-on-iref-updates 尚未(yet)实现。
  • 未记录的 CustomPred 协议未实现 -- 你可以使用普通的 is,并在需要该级别控制时扩展 clojure.testassert-expr 多方法。

测试与开发

要测试,请运行 clj -X:test(针对 Clojure 1.9 的测试)。

多版本测试:

for v in 1.9 1.10 1.11 1.12
do
  clojure -X:test:$v
done

你也可以启用 Humane Test Output 来运行测试,但你需要排除负面测试,因为它们对 HTO 会修改的测试报告数据做出了假设:

for v in 1.9 1.10 1.11 1.12
do
  clojure -X:test:$v:humane :excludes '[:negative]'
done

ClojureScript 测试

ClojureScript 版本需要自托管的 ClojureScript(具体为 planck)。 一旦 planck -h 可用, 你就可以使用以下命令运行 ClojureScript 测试:

clojure -M:cljs-runner -e :negative

如果你修改 test.cljc 中的一行,也可以运行负面测试, 参见包含 (def humane-test-output? 的行下方的注释。

ClojureScript REPL

在 REPL 中尝试一些操作可能很有用。 你可以通过以下方式运行 ClojureScript 的 REPL:

$ planck --compile-opts planckopts.edn -c `clojure -Spath -A:humane` -r
ClojureScript 1.10.520
cljs.user=> (require '[expectations.clojure.test :refer-macros [defexpect expect]])
nil
cljs.user=> (defexpect a (expect number? 1))
#'cljs.user/a
cljs.user=> (a)
nil
cljs.user=> (defexpect a (expect number? :b))
#'cljs.user/a
cljs.user=> (a)

FAIL in (a) (run_block@file:44:173)


expected: (=? number? :b)
  actual: (not (number? :b))
nil
cljs.user=>

这将为你配置 defexpectexpect。 根据需要添加其他内容。

许可证与版权

版权所有 © 2018-2024 Sean Corfield,保留所有权利。

根据 Eclipse 公共许可证 1.0 版本分发。