2015-06-10 6 views
13

Ich versuche Komponententests zu schreiben, wenn Sie core.async go macros verwenden. Wenn der Test naiv wie folgt geschrieben wird, erscheint der Code in den Go-Blöcken nicht ausgeführt.Wie teste ich Einheit clojure.core.async gehen Makros?

(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")))) 

Ich habe es geschafft, das folgende funktioniert, aber es ist extrem 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)]))) 

Irgendwelche Vorschläge, wie ich das richtig machen kann?

Antwort

3

Ihr Test wird beendet und schlägt fehl. Dies geschieht, zuverlässiger, wenn ich einen Schlaf in setzen und es dann machen scheitern:

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")) 

hier können wir darüber berichten, dass nichts ausfällt, dann druckt er die Fehlermeldung. Wir können das beheben, indem wir das Ende des Tests explizit koordinieren und diese Aktion beenden, wie bei den meisten Lösungen in core.async, indem wir einen weiteren chan hinzufügen.

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} 

Das entspricht Ihrer Lösung mit Alts. Ich glaube nicht, dass deine Lösung Hackey ist. Bei asynchronem Code muss immer darauf geachtet werden, wenn die Dinge zu Ende sind, auch wenn Sie sich bewusst dafür entscheiden, das Ergebnis zu ignorieren.

21

Tests werden synchron ausgeführt. Wenn Sie also asynchron sind, wird der Test-Runner nicht ausgeführt. In Clojure müssen Sie den Test-Runner über <!! blockieren, in ClojureScript müssen Sie ein asynchrones Testobjekt zurückgeben. Das ist eine allgemeine Hilfsfunktion I in allen CLJC Tests meine async verwenden:

(defn test-async 
    "Asynchronous test awaiting ch to produce a value or close." 
    [ch] 
    #?(:clj 
    (<!! ch) 
    :cljs 
    (async done 
     (take! ch (fn [_] (done)))))) 

Ihr Test es, CLJC kompatibel und suchen viel weniger „Hacky“ mit:

(deftest test1 
    (let [ch (chan)] 
    (go (>! ch "Hello")) 
    (test-async 
     (go (is (= "Hello" (<! ch))))))) 

Es ist gute Praxis zu behaupten dass der Test unblockiert, insbesondere während der testgetriebenen Entwicklung, bei der Sie verhindern möchten, dass Ihr Test Runner gesperrt wird. Sperren ist auch eine häufige Ursache für Fehler bei der asynchronen Programmierung. Daher ist das Testen sehr sinnvoll.

zu tun, dass ich einen Helfer ähnlich wie Ihre Timeout Sache schrieb:

(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))) 

Sie können es verwenden, um den Test zu schreiben wie:

(deftest test1 
    (let [ch (chan)] 
    (go (>! ch "Hello")) 
    (test-async 
     (test-within 1000 
     (go (is (= "Hello" (<! ch))))))) 
+1

Fehlen eines Parens im zweiten Codeblock. – tar

+0

@tar: Danke, behoben. –

0

Ich verwende einen ähnlichen Ansatz wie Leons , aber ohne zusätzliche go Blöcke:

(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)))) 

können Sie es einfach als:

Die meiste Zeit möchte ich nur den Wert aus dem Kanal ohne zusätzlichen Aufwand lesen.

+1

Meine Lösung ist so konzipiert, dass sie sowohl in Clojure als auch in Clojurescript funktioniert, um das Schreiben plattformübergreifender Komponententests zu ermöglichen. –