【问题标题】:How do I unit test clojure.core.async go macros?如何对 clojure.core.async go 宏进行单元测试?
【发布时间】:2015-08-26 06:55:04
【问题描述】:

我正在尝试在使用 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)])))

关于如何正确执行此操作的任何建议?

【问题讨论】:

    标签: unit-testing clojure macros core.async


    【解决方案1】:

    您的测试正在完成,然后失败。如果我进入睡眠状态然后让它失败,这种情况会更可靠地发生:

    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 中的大多数解决方案一样,添加一个通道。

    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 的解决方案。我不认为你的解决方案是hackey。使用异步代码时,总是需要注意事情何时完成,即使您有意识地决定忽略结果。

    【讨论】:

      【解决方案2】:

      测试是同步执行的,所以如果你异步,测试运行器不会。在 Clojure 中,您需要通过 &lt;!! 阻止测试运行器,在 ClojureScript 中,您必须返回一个异步测试对象。这是我在所有异步 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)))))))
      

      【讨论】:

        【解决方案3】:

        我正在使用类似于 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)))
        

        大多数时候我只是想从通道中读取值而不需要任何额外的麻烦。

        【讨论】:

        • 我的解决方案设计用于 Clojure 和 Clojurescript,以允许编写跨平台单元测试。
        猜你喜欢
        • 2017-06-21
        • 1970-01-01
        • 2019-10-12
        • 2012-01-08
        • 2019-11-02
        • 1970-01-01
        • 1970-01-01
        • 2020-06-22
        • 2021-07-01
        相关资源
        最近更新 更多