2015-06-10 16 views
13

Sto provando a scrivere i test unitari quando si utilizzano i macro core.async go. Scrivere il test in modo ingenuo, come segue, sembra che il codice all'interno dei blocchi di go non venga eseguito.Come si esegue il test dell'unità clojure.core.async go macro?

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

Sono riuscito a ottenere il seguente funzionamento, ma è estremamente 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)]))) 

Qualche suggerimento su come posso farlo correttamente?

risposta

3

il test sta terminando e quindi non riesce. Questo accade in modo più affidabile se ho messo un sonno in e poi fare fallire:

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

qui possiamo vederlo segnalare che nulla viene a mancare, allora stampa il messaggio di errore. Possiamo risolvere questo problema coordinando esplicitamente la fine del test e completando l'azione, come la maggior parte delle soluzioni in core.async, aggiungendo un altro 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} 

Che è equivalente alla soluzione con alts. Non penso che la tua soluzione sia hackey. Con il codice asincrono è sempre necessario prestare attenzione a quando le cose finiscono, anche se si decide consapevolmente di ignorare il risultato.

21

I test vengono eseguiti in modo sincrono, quindi se si procede in modo asincrono il test-runner non lo farà. In Clojure è necessario bloccare il runner di test tramite <!!, in ClojureScript è necessario restituire un oggetto di test asincrono. Questa è una funzione generica di supporto che uso in tutte le mie prove CLJC asincroni:

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

Il test utilizza, CLJC compatibile e cercando il modo meno "hacky":

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

E 'buona norma far valere che il test si sblocca, specialmente durante lo sviluppo guidato da test in cui si desidera evitare di bloccare il runner di test. Inoltre, il blocco è una causa comune di errore nella programmazione asincrona, quindi testare contro di esso è altamente ragionevole.

per farlo ho scritto un aiutante simile al vostro timeout cosa:

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

lo si può utilizzare per scrivere il test come:

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

Manca un paren nel secondo blocco di codice. – tar

+0

@tar: Grazie, corretto. –

0

sto usando un approccio simile a Leon di , ma senza blocchi go aggiuntivi:

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

si può utilizzare semplicemente come:

(is (= 42 (<!!? result-chan))) 

La maggior parte delle volte voglio solo leggere il valore dal canale senza alcun problema aggiuntivo.

+1

La mia soluzione è progettata per funzionare sia in Clojure che in Clojurescript per consentire la scrittura di test di unità multipiattaforma. –