2

一般来说,我是 Cursive 和 Clojure 的新手,在获得体面的 TDD 工作流程方面遇到了一些困难。

我的问题是后续测试运行取决于 REPL 中的状态。例如,假设您有以下代码。

(def sayHello "hello")

(deftest test-repl-state
  (testing "testing state in the repl"
      (is (= "hello" sayHello)))) 

如果您使用“Tools->REPL->Run tests in current ns in REPL”运行它,它将通过。

如果你然后像这样重构代码

(def getGreeting "hello")

(deftest test-repl-state
  (testing "testing state in the repl"
      (is (= "hello" sayHello))))  

如果你使用“Tools->REPL->Run tests in current ns in REPL”运行它,它仍然会通过(因为sayHellorepl 中仍然存在 def )。但是,测试应该失败,因为代码当前处于失败状态(sayHello未在代码中的任何地方定义)。

我尝试在 REPL 窗口中切换“将清除本地人”按钮,但这似乎无法解决问题。

如果有一种方法可以在 REPL 之外运行测试(或者在每次测试运行的新 REPL 中),我可以将其作为解决方案。

我想要的只是被测源代码和测试结果之间存在1对1的对应关系。

在此先感谢您的帮助。

4

5 回答 5

0

是的,旧defs 可用很烦人。我什至通常不创建测试(哎呀),但这在正常开发过程中会咬我。如果我创建一个函数,然后重命名它,然后更改它,然后不小心引用了第一个函数名称,我会得到奇怪的结果,因为它引用的是旧函数。我仍在寻找一种不涉及杀死和重新启动 REPL 的好方法。

但是,对于您的特定情况,有一些简单但较差的解决方法:

  • 打开 IntelliJ 的终端(窗口左下方的按钮)并运行lein test. 这将执行所有项目的测试并报告结果。

  • 与上述类似,您可以在 IntelliJ 之外,在项目目录中打开一个命令窗口并运行lein test,它将运行所有找到的测试。

您还可以指定使用哪个名称空间进行测试lein test <ns here>(例如lein test beings-retry.core-test),或者使用名称空间中的特定测试:only(例如lein test :only beings-retry.core-test/a-test; where a-testis a deftest)。不幸的是,这在 REPL 中不会发生,因此它会破坏工作流程。


如上所述,我所知道的唯一基于 REPL 的解决方法就是杀死 REPL:

  • “停止 REPL” (Ctrl+F2)
  • “重新连接”(Ctrl+F5)。

当然,如果你经常这样做,这很慢,而且是一个糟糕的解决方案。我很想看看其他人是否有更好的解决方案。

于 2018-11-12T18:38:20.773 回答
0

感谢大家的建议。我正在发布我自己对这个问题的答案,因为我找到了一种适合我的前进方式,我不确定上述任何一个都是我正在寻找的。

我得出的结论是,虽然 clojure REPL 很有用,但我不会在其中运行测试。这基本上归结为在每次测试运行之间运行命令以清理 repl (如refreshtools.namespace https://github.com/clojure/tools.namespace中非常有用的功能)或不运行测试之间的选择回复。

我选择了后一个选项,因为。

  1. 少了一步(并且重新加载并不总是完美的)
  2. CI 测试不在 REPL 中运行,因此直接在 dev 中运行它们更接近 CI 环境。
  3. 生产中的代码也不在 REPL 中运行,因此在 repl 之外运行测试更接近生产代码的运行方式。

实际上,在 IntelliJ 中配置运行配置以将应用程序中的单个测试或所有测试作为普通的 clojure 应用程序运行是一件非常简单的事情。如果您愿意,您甚至可以同时运行 REPL,并随心所欲地使用它。工具如此倾向于在​​ REPL 中运行的事实在某种程度上使我对这个选项视而不见。

用草书运行测试

我对 Clojure 非常缺乏经验,也是一个顽固的老山羊,它以他的 TDD 方式设置,但至少其他一些人同意我对此https://github.com/cursive-ide/cursive/issues/247的看法。

此外,如果有人感兴趣,这里有一个关于 REPL 如何保持状态以及这如何导致各种奇怪行为的精彩演讲https://youtu.be/-RaFcpNiYCo。事实证明,我在重新定义函数时看到的问题只是冰山一角。

于 2018-11-15T20:22:33.257 回答
0

您可以使用lein 插件的内置测试缩小(测试选择器)功能。每次保存文件时test-refresh,它只允许测试那些已用 meta 标记的测试。^:test-refresh/focus

于 2018-11-12T20:17:00.133 回答
0

一种可能会有所帮助的选项,尤其是在您捆绑多个断言或重复测试时let。名称-值绑定有一个已知的范围,并且可以避免您重新键入很多内容。

这是一个例子:

(deftest my-bundled-and-scoped-test
   (let [TDD    "My expected result"
         helper (some-function :data)]
     (testing "TDD-1: Testing state in the repl"
       (is (= TDD "MY expected result")))
     (testing "TDD-2: Reusing state in the repl"
       (is (= TDD helper)))))

一旦my-bundled-and-scoped测试完成执行,您将不再处于let绑定中。另一个好处是结果也是some-function可重用的,这对于测试同一函数/输入对的多个断言或属性非常方便。

在这个主题上,我还建议使用Leiningen来运行您的测试,因为有很多插件可以帮助您更有效地进行测试。我会检查test-refreshspecljcloverage

于 2018-11-16T22:57:34.390 回答
0

此类问题的通常解决方案是stuartsierra/componenttolitius/mount

完整的描述在这里不合适,但总体思路是让一些系统以允许干净地重新加载应用程序状态的方式管理状态。这有助于在以交互方式在正在运行的系统上工作时保持接近保存在源文件中的代码。

于 2018-11-12T22:42:28.160 回答