簡體   English   中英

`with-redefs`不綁定某些功能(Clojure)

[英]`with-redefs` not binding certain functions (Clojure)

我正在嘗試在Clojure中測試“井字游戲”命令行游戲,並希望重新定義使用read-line來模擬用戶輸入的功能。 但是, with-redefs不能按我預期的那樣工作,並且(似乎)與其他測試有所不同。

這是我的測試

(ns game.setup-test
  (:require [clojure.test :refer :all]
            [clojure.string :as st]
            [game.helpers :as gh]
            [game.setup :refer :all]))


(deftest initial-setup-test
  (testing "Can set initial options"
    (with-redefs [get-game-type-input (fn [] "1")
                  get-marker-input (fn [] "X") 
                  get-starter-input (fn [] "1")]
      (do (initial-setup my-game)
       (is (= :human (get-in @my-game [:player1 :type])))
       (is (= :human (get-in @my-game [:player2 :type])))
       (is (= [:player1 :player2] (get @my-game :up-next)))
       (is (= "X" (get-in @my-game [:player1 :marker])))
       (is (= "O" (get-in @my-game [:player2 :marker])))))))

這是我在setup.clj函數定義

(ns game.setup)

(defn get-input [requestor validator]
  (fn [] (do (requestor)
             (if-let [input (validator (read-line))]
                      input
                      (recur)))))

(def get-marker-input (get-input request-p1-marker valid-marker?))
(def get-game-type-input (get-input request-game-type valid-type?))
(def get-starter-input (get-input request-starter-type valid-starter?))


(def set-starter
  (partial set-game-option
           get-starter-input
           starter-opts
           swap-merge
           print-starter-player))

(defn initial-setup [game-state]
  (do (set-game-type game-state)
      (set-player-markers game-state)
      (set-starter game-state)))

(defn set-game-option [input-getter attr-opts updater printer game]
  (-> (input-getter)      ; gets user input
      attr-opts           ; returns corresponding update-map
      (#(updater game %)) ; updates the game-state with update-map
      printer))           ; prints the updated game-state

運行此測試時。 我仍然看到request-*函數的STDOUT輸出,並且測試等待輸入到STDIN。 因此,我假設with-redefs不會像我期望的那樣“重新定義” get-*函數。

符合我預期的另一項測試

(deftest get-marker-input-test
  (testing "Reads perfect input"
    (with-redefs [read-line (fn [] "X")]
      (is (= "X" (get-marker-input))))))

任何幫助表示贊賞! :)

您未看到重新定義生效的原因是,通過initial-setup在測試set-starter下的函數使用了partial partial返回一個新函數,該函數保留對先前定義的函數的引用。

一個簡單的例子:

boot.user=> (def my-dumb-fn (partial str "prefix"))
#'boot.user/my-dumb-fn
boot.user=> (with-redefs [str (fn [s] "hello")] (str 23))
"hello"
boot.user=> (with-redefs [str (fn [s] "hello")] (my-dumb-fn 23))
"prefix23"

嘗試不使用read-line而是使用read :將輸入字符串讀入數據結構而不是字符串。 此外,當它找到一個空格(或閱讀形式的結尾,說得更准確)時,它將停止讀取輸入內容,從而避免消耗其余內容。

with-in-str的特殊內置宏允許您為測試目的指定某些特定輸入,如下所示:

(with-in-str "X 0 X 0 0 X"
  (let [turn1 (read)
        turn2 (read)
        ;; more reads ...
        ]
    [turn1
     turn2
     ;; more read values
     ]))

返回

[X 0]

換句話說,不需要重新定義功能。

就像@dpassen所說的,不要使用partial (我從不這樣做)。 這是一個替代方案:

> lein new tupelo-app fred     ; make a new project `fred` with nice defaults
> cd fred

剪切並粘貼,如下所示:

~/expr/fred > cat src/tst/fred/core.clj
(ns tst.fred.core
  (:use fred.core tupelo.core tupelo.test)
  (:require 
    [clojure.java.io :as io]
    [tupelo.string :as ts] )
  (:import [fred Calc]))

(def x 5)
(def  p1     (partial + x))
(defn p2 [y] (+ x y))

(dotest
  (spyx (p1 2))
  (spyx (p2 2))
  (with-redefs [x 10]
    (spyx (p1 3))
    (spyx (p2 3))))

我們得到結果:

---------------------------------
   Clojure 1.9.0    Java 9.0.1
---------------------------------
Testing fred.core

Testing tst.fred.core
(p1 2) => 7
(p2 2) => 7

(p1 3) => 8
(p2 3) => 13

因此,通過將p2定義為一個函數(而不是帶有partial的“ constant” var ),我們可以獲得預期的結果,並且with-redefs可以正常工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM