About testing private functions

43 views
Skip to first unread message

Chanwoo Yoo

unread,
Nov 10, 2008, 9:10:31 AM11/10/08
to Clojure
I'm sorry for spamming.. In Stuart's book - 'Programming Clojure', I
saw below paragraph:

"1. Redefine private to mean "private for production code, but public
for serialization and unit tests".
...

I have seen Java programs that needed all these features."

So I decided to try what he referred to. I thought it will be trivial
by using macros.. But shamefully, I couldn't do this simple job.. Here
is my code, which does not work:


;;;;;;;;TRY 1;;;;;;;;;
;;defn_star.clj
(ns defn-star)

(def *test* (ref false))

(defmacro defn*
{:doc "If *test* is true, 'defn*' expands to 'defn', otherwise it
expands to 'defn-'"}
[& body]
(if (true? @*test*)
`(defn ~@body)
`(defn- ~@body)))

;;temp.clj
(ns temp
(:use defn-star))

(dosync (alter *test* #(not %)))

(defn* sym->key
{:doc "(sym->key 'abc) => :abc (sym->key nil) => nil"}
[sym]
(if (= "" (str sym))
nil
(keyword (str sym))))

;;test_temp.clj
(ns test-temp
(:use temp)
(:use (clojure.contrib test-is))

(deftest test-sym->key
(is (= (sym->key 'abc) :abc)))

(run-tests)

;;;;;;;;;;TRY 2;;;;;;;;;;
;;defn_star.clj
(ns defn-star)

(def *test* false)

(defmacro defn*
{:doc "If *test* is true, 'defn*' expands to 'defn', otherwise it
expands to 'defn-'"}
[& body]
(if (true? *test*)
`(defn ~@body)
`(defn- ~@body)))

;;temp.clj
(ns temp
(:use defn-star))

(binding [*test* true]
(defn* sym->key
{:doc "(sym->key 'abc) => :abc (sym->key nil) => nil"}
[sym]
(if (= "" (str sym))
nil
(keyword (str sym)))))

;;test_temp.clj
(ns test-temp
(:use temp)
(:use (clojure.contrib test-is))

(deftest test-sym->key
(is (= (sym->key 'abc) :abc)))

(run-tests)

In both cases, tests can not recognize 'sym->key'. Though I (:use defn-
star), it seems that *test* variable is not referred by 'temp'. Ahh..
maybe I think that I didn't properly understand about namespaces. Is
there any elegant way to achieve testing private functions? Any
suggestion appreciated.

mb

unread,
Nov 10, 2008, 9:32:00 AM11/10/08
to Clojure
Hi,

simply change to the namespace in question:

in <classpath>/foo/foo.clj:
---8<---
(ns foo)

(defn- private-foo-func
[x y]
(+ x y))
---8<---

in test-foo.clj:
---8<---
; Make sure foo namespace is loaded
; correctly before we go on.
(require 'foo)

(in-ns 'foo)

; Make sure, we don't interfere with
; foo's vars.
(require '[clojure.contrib.test-is :as test])

(test/deftest test-private-foo-func
(test/is (= (private-foo-func 1 2) 3)))

(test/run-tests)
---8<---

In Clojure:
---8<---
user=> (require 'foo)
nil
user=> (foo/private-foo-func 1 2)
java.lang.IllegalStateException: var: #=(var foo/private-foo-func) is
not public (NO_SOURCE_FILE:2)
user=> (load-file "test-foo.clj")
Testing #<Namespace: foo>

Ran 1 tests with 1 assertions.
0 failures, 0 exceptions.
nil
user=>
---8<---

I don't know whether this is the recommended way of doing things,
but it works....

Sincerely
Meikel

Stuart Halloway

unread,
Nov 10, 2008, 9:49:09 AM11/10/08
to clo...@googlegroups.com
Hi Chanwoo,

I think Meikel's approach of switching to the namespace is simpler,
but your "try 1" works for me. Is it possible that you need to reload
all the namespaces in your REPL to clear out some old code?

Try 2 cannot work because the defn* gets expanded at macro-time, and
the binding of *test* to true happens later.

Cheers,
Stuart

Chanwoo Yoo

unread,
Nov 10, 2008, 11:47:56 AM11/10/08
to Clojure
You're right. Meikel's way seems good to me, too, and "try 1" works
after restarting slime. :) Thanks! Meikel, Stuart~

On 11월10일, 오후11시49분, Stuart Halloway <stuart.hallo...@gmail.com>
wrote:
Reply all
Reply to author
Forward
0 new messages