如何对 clojure.core.async go 宏进行单元测试?
How do I unit test clojure.core.async go macros?
我正在尝试在使用 core.async go 宏时编写单元测试。天真地写测试,如下,go块里面的代码好像没有执行。
(ns app.core-test
(:require [clojure.test :refer :all]
[clojure.core.async :as async]))
(deftest test1 []
(let [chan (async/chan)]
(async/go
(is (= (async/<! chan) "Hello")))
(async/go
(async/>! chan "Hello"))))
我已经设法让以下工作正常进行,但它非常 hacky。
(deftest test1 []
(let [result (async/chan)
chan (async/chan)]
(async/go
(is (= (async/<! chan) "Hello"))
(async/>! result true))
(async/go
(async/>! chan "Hello"))
(async/alts!! [result (async/timeout 10000)])))
关于如何正确执行此操作有什么建议吗?
您的测试即将完成,然后失败。如果我先睡一觉然后让它失败,这种情况会更可靠地发生:
user> (deftest test1 []
(async/<!!
(let [chan (async/chan)]
(async/go
(async/go
(async/<! (async/timeout 1000))
(is (= (async/<! chan) "WRONG")))
(async/go
(async/>! chan "Hello"))))))
#'user/test1
user> (clojure.test/run-tests)
Testing user
Ran 1 tests containing 0 assertions.
0 failures, 0 errors.
{:test 1, :pass 0, :fail 0, :error 0, :type :summary}
user>
FAIL in (test1) (form-init8563497779572341831.clj:5)
expected: (= (async/<! chan) "WRONG")
actual: (not (= "Hello" "WRONG"))
在这里我们可以看到它报告没有失败,然后它打印失败消息。我们可以通过明确协调测试结束和该动作来解决这个问题,就像 core.async 中的大多数解决方案一样,再添加一个 chan。
user> (deftest test1 []
(async/<!!
(let [all-done-chan (async/chan)
chan (async/chan)]
(async/go
(async/go
(async/<! (async/timeout 1000))
(is (= (async/<! chan) "WRONG"))
(async/close! all-done-chan ))
(async/go
(async/>! chan "Hello"))
(async/<! all-done-chan)))))
#'user/test1
user> (clojure.test/run-tests)
Testing user
FAIL in (test1) (form-init8563497779572341831.clj:6)
expected: (= (async/<! chan) "WRONG")
actual: (not (= "Hello" "WRONG"))
Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:test 1, :pass 0, :fail 1, :error 0, :type :summary}
这相当于您使用 alts 的解决方案。我不认为你的解决方案是 hackkey。对于异步代码,始终需要注意事情何时完成,即使您有意识地决定忽略结果。
测试是同步执行的,所以如果你异步执行,测试运行器就不会。在 Clojure 中,您需要通过 <!!
来阻止测试运行程序,在 ClojureScript 中,您必须 return 一个异步测试对象。这是我在所有异步 CLJC 测试中使用的通用辅助函数:
(defn test-async
"Asynchronous test awaiting ch to produce a value or close."
[ch]
#?(:clj
(<!! ch)
:cljs
(async done
(take! ch (fn [_] (done))))))
你的测试使用它,兼容 CLJC 并且看起来更少 "hacky":
(deftest test1
(let [ch (chan)]
(go (>! ch "Hello"))
(test-async
(go (is (= "Hello" (<! ch)))))))
断言测试解除阻塞是一种很好的做法,尤其是在您希望避免锁定测试运行器的测试驱动开发期间。此外,锁定是异步编程失败的常见原因,因此对其进行测试是非常合理的。
为此,我编写了一个类似于您的超时工具的帮助程序:
(defn test-within
"Asserts that ch does not close or produce a value within ms. Returns a
channel from which the value can be taken."
[ms ch]
(go (let [t (timeout ms)
[v ch] (alts! [ch t])]
(is (not= ch t)
(str "Test should have finished within " ms "ms."))
v)))
您可以使用它来编写您的测试:
(deftest test1
(let [ch (chan)]
(go (>! ch "Hello"))
(test-async
(test-within 1000
(go (is (= "Hello" (<! ch)))))))
我正在使用类似于 Leon 的方法,但没有额外的 go 块:
(defn <!!?
"Reads from chan synchronously, waiting for a given maximum of milliseconds.
If the value does not come in during that period, returns :timed-out. If
milliseconds is not given, a default of 1000 is used."
([chan]
(<!!? chan 1000))
([chan milliseconds]
(let [timeout (async/timeout milliseconds)
[value port] (async/alts!! [chan timeout])]
(if (= chan port)
value
:timed-out))))
您可以简单地使用它:
(is (= 42 (<!!? result-chan)))
大多数时候我只是想从通道中读取值而不需要任何额外的麻烦。
我正在尝试在使用 core.async go 宏时编写单元测试。天真地写测试,如下,go块里面的代码好像没有执行。
(ns app.core-test
(:require [clojure.test :refer :all]
[clojure.core.async :as async]))
(deftest test1 []
(let [chan (async/chan)]
(async/go
(is (= (async/<! chan) "Hello")))
(async/go
(async/>! chan "Hello"))))
我已经设法让以下工作正常进行,但它非常 hacky。
(deftest test1 []
(let [result (async/chan)
chan (async/chan)]
(async/go
(is (= (async/<! chan) "Hello"))
(async/>! result true))
(async/go
(async/>! chan "Hello"))
(async/alts!! [result (async/timeout 10000)])))
关于如何正确执行此操作有什么建议吗?
您的测试即将完成,然后失败。如果我先睡一觉然后让它失败,这种情况会更可靠地发生:
user> (deftest test1 []
(async/<!!
(let [chan (async/chan)]
(async/go
(async/go
(async/<! (async/timeout 1000))
(is (= (async/<! chan) "WRONG")))
(async/go
(async/>! chan "Hello"))))))
#'user/test1
user> (clojure.test/run-tests)
Testing user
Ran 1 tests containing 0 assertions.
0 failures, 0 errors.
{:test 1, :pass 0, :fail 0, :error 0, :type :summary}
user>
FAIL in (test1) (form-init8563497779572341831.clj:5)
expected: (= (async/<! chan) "WRONG")
actual: (not (= "Hello" "WRONG"))
在这里我们可以看到它报告没有失败,然后它打印失败消息。我们可以通过明确协调测试结束和该动作来解决这个问题,就像 core.async 中的大多数解决方案一样,再添加一个 chan。
user> (deftest test1 []
(async/<!!
(let [all-done-chan (async/chan)
chan (async/chan)]
(async/go
(async/go
(async/<! (async/timeout 1000))
(is (= (async/<! chan) "WRONG"))
(async/close! all-done-chan ))
(async/go
(async/>! chan "Hello"))
(async/<! all-done-chan)))))
#'user/test1
user> (clojure.test/run-tests)
Testing user
FAIL in (test1) (form-init8563497779572341831.clj:6)
expected: (= (async/<! chan) "WRONG")
actual: (not (= "Hello" "WRONG"))
Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:test 1, :pass 0, :fail 1, :error 0, :type :summary}
这相当于您使用 alts 的解决方案。我不认为你的解决方案是 hackkey。对于异步代码,始终需要注意事情何时完成,即使您有意识地决定忽略结果。
测试是同步执行的,所以如果你异步执行,测试运行器就不会。在 Clojure 中,您需要通过 <!!
来阻止测试运行程序,在 ClojureScript 中,您必须 return 一个异步测试对象。这是我在所有异步 CLJC 测试中使用的通用辅助函数:
(defn test-async
"Asynchronous test awaiting ch to produce a value or close."
[ch]
#?(:clj
(<!! ch)
:cljs
(async done
(take! ch (fn [_] (done))))))
你的测试使用它,兼容 CLJC 并且看起来更少 "hacky":
(deftest test1
(let [ch (chan)]
(go (>! ch "Hello"))
(test-async
(go (is (= "Hello" (<! ch)))))))
断言测试解除阻塞是一种很好的做法,尤其是在您希望避免锁定测试运行器的测试驱动开发期间。此外,锁定是异步编程失败的常见原因,因此对其进行测试是非常合理的。
为此,我编写了一个类似于您的超时工具的帮助程序:
(defn test-within
"Asserts that ch does not close or produce a value within ms. Returns a
channel from which the value can be taken."
[ms ch]
(go (let [t (timeout ms)
[v ch] (alts! [ch t])]
(is (not= ch t)
(str "Test should have finished within " ms "ms."))
v)))
您可以使用它来编写您的测试:
(deftest test1
(let [ch (chan)]
(go (>! ch "Hello"))
(test-async
(test-within 1000
(go (is (= "Hello" (<! ch)))))))
我正在使用类似于 Leon 的方法,但没有额外的 go 块:
(defn <!!?
"Reads from chan synchronously, waiting for a given maximum of milliseconds.
If the value does not come in during that period, returns :timed-out. If
milliseconds is not given, a default of 1000 is used."
([chan]
(<!!? chan 1000))
([chan milliseconds]
(let [timeout (async/timeout milliseconds)
[value port] (async/alts!! [chan timeout])]
(if (= chan port)
value
:timed-out))))
您可以简单地使用它:
(is (= 42 (<!!? result-chan)))
大多数时候我只是想从通道中读取值而不需要任何额外的麻烦。