clojure:优雅地模拟具有不同值的函数

时间:2013-06-10 18:22:03

标签: unit-testing clojure

在编写单元测试时,我有时需要模拟一个函数来为每个函数调用返回一系列定义的值。

目前我正在做这样的事情:

(testing "fitness-proportionate selection"
  ; store sequence of rand values in atom, first val is ignored as we always use rest
  ; normalized fitness: 0: 0 1: 1/6 2: 1/3 3: 1/2
  ; summed fitness 0: 0 1: 1/6 2: 1/2 3: 1
  (let [r (atom [0 1 1/2 1/6 0])] (with-redefs [rand (fn [] (first (swap! r rest)))]
    (is (= [3 2 1 0] (fitness-proportionate-selection [0 1 2 3] identity))))))

有人可以帮助我找到更优雅的方法吗? 更具可读性的东西,包含更少的逻辑。 这样可以减少单元测试本身的错误。 我目前正在使用clojure.test,并且不希望使用其他库。

1 个答案:

答案 0 :(得分:3)

我想不出一种模仿rand函数的替代方法,除了让某种引用保存它应该返回的值序列。这是有道理的,因为rand本身就是一个从其他(伪随机)源生成其值的函数。

那就是说,我会创建一个高阶函数,它根据一系列数字返回一个数字生成器,而不是在测试代码中嵌入该逻辑。

(defn gen-rand
  "Returns a no args function that mocks `rand`,
  which returns on each call a number from s in 
  the same order provided."
  [s]
  (let [x (atom s)]
    #(let [n (first @x)] 
       (swap! x rest)
       n)))

(defn fitness-proportionate-selection
  "Mock function."
  [s f]
  (vec (repeatedly 4 rand)))

(testing "fitness-proportionate selection"
  (with-redefs [rand (gen-rand [1 1/2 1/6 0])]
    (is (= [1 1/2 1/6 0] (fitness-proportionate-selection [0 1 2 3] identity)))))

请注意,我更改了代码,以便返回提供给gen-rand的序列中的所有值,并且不会丢弃第一个