`with-redefs`不绑定某些函数(Clojure)

时间:2018-01-21 20:50:53

标签: unit-testing clojure

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

任何帮助表示赞赏! :)

3 个答案:

答案 0 :(得分:3)

您没有看到重新生效的原因是,set-starter通过initial-setup测试的功能使用了partialpartial返回一个新函数,该函数保留对先前定义的函数的引用。

一个更简单的例子:

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定义为函数(而不是"常量" varpartial),我们得到预期的结果{{1}工作正常。