我通常对Cursive和Clojure还是陌生的,在获得像样的TDD工作流程方面有些困难。
我的问题是,后续测试运行取决于REPL中的状态。例如,假设您具有以下代码。
(def sayHello "hello")
(deftest test-repl-state
(testing "testing state in the repl"
(is (= "hello" sayHello))))
如果使用“工具-> REPL->在REPL中的当前ns中运行测试”来运行它,则会通过。
如果您随后重构代码
(def getGreeting "hello")
(deftest test-repl-state
(testing "testing state in the repl"
(is (= "hello" sayHello))))
如果使用“工具-> REPL->在REPL中的当前ns中运行测试”来运行它,它将仍然通过(因为sayHello
的定义仍然存在于repl中)。但是,测试应该失败,因为代码当前处于失败状态(在代码中的任何地方都未定义sayHello
。
我尝试切换REPL窗口中的“将清除本地”按钮,但这似乎无法解决问题。
如果有一种方法可以在REPL之外运行测试(或在每次测试运行中都在新的REPL中运行),那么可以将其作为解决方案。
我想要的是被测源代码和测试结果之间存在1到1的对应关系。
在此先感谢您的帮助。
答案 0 :(得分:0)
是的,拥有旧的def
很烦人。我什至通常不创建测试(抱怨),但这在正常开发过程中使我感到痛苦。如果我创建一个函数,然后对其重命名,然后对其进行更改,然后不小心引用第一个函数名称,由于引用的是旧函数,我会得到奇怪的结果。我仍在寻找解决此问题的好方法,该方法不涉及终止和重新启动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
)指定名称空间中的特定测试;其中a-test
是deftest
)。不幸的是,REPL中不会发生这种情况,因此会中断工作流程。
如上所述,我知道的唯一基于REPL的解决方法是杀死REPL:
当然,这很慢,而且如果您不断这样做,这将是一个糟糕的解决方案。我有兴趣看看是否还有其他更好的解决方案。
答案 1 :(得分:0)
您可以使用test-refresh
lein插件的Built-in test narrowing (test selector)功能。每次保存文件时,它仅允许测试标记有^:test-refresh/focus
元的那些测试。
答案 2 :(得分:0)
解决此类问题的常用方法是stuartsierra/component
或tolitius/mount
。
完整的描述在这里不合时宜,但是一般的想法是拥有一些系统来管理状态,该系统允许干净地 reload 应用程序状态。当在正在运行的系统上进行交互工作时,这有助于保持与保存在源文件中的代码接近。
答案 3 :(得分:0)
感谢大家的建议。我发布了自己的解决方案,因为我已经找到了适合自己的前进方式,并且不确定上述任何内容是否正是我想要的。
我得出的结论是clojure REPL虽然有用,但并不是我进行测试的地方。基本上,可以选择在运行命令以清除每次测试运行之间的副本(例如tools.namespace https://github.com/clojure/tools.namespace中非常有用的refresh
函数)还是不在REPL中运行测试之间进行选择。
我之所以选择后者,是因为。
在IntelliJ中配置运行配置,以将应用程序中的单个测试或所有测试作为普通clojure应用程序运行实际上是一件非常简单的事情。如果愿意,甚至可以同时运行一个REPL,并根据需要使用它。该工具非常倾向于REPL中的运行,这一事实在某种程度上使我对这种选择视而不见。
我对Clojure缺乏经验,而且对以TDD方式设置的顽固的老山羊也没有经验,但是至少有一些其他人对此https://github.com/cursive-ide/cursive/issues/247表示同意。
如果有人感兴趣,那么在https://youtu.be/-RaFcpNiYCo上有很多关于REPL如何保持状态以及如何引起各种奇怪行为的演讲。事实证明,我在重新定义函数时遇到的问题只是冰山一角。
答案 4 :(得分: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-refresh,speclj和cloverage。