我正在尝试在Clojure中测试一个“tic tac toe”命令行游戏,并希望重新定义使用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))))))
任何帮助表示赞赏! :)
答案 0 :(得分:3)
您没有看到重新生效的原因是,set-starter
通过initial-setup
测试的功能使用了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"
答案 1 :(得分:2)
尝试不使用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]
换句话说,没有必要重新定义函数。
答案 2 :(得分:-2)
正如@dpassen所说,不要使用partial
(我从来没有这样做过)。这是另一种选择:
> lein new tupelo-app fred ; make a new project `fred` with nice defaults
> cd fred
Cut&粘贴所以看起来像这样:
~/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
定义为函数(而不是"常量" var
和partial
),我们得到预期的结果{{1}工作正常。