The code I am testing implements a simple budget system for my wife and I. It's fairly small but it will allow me to learn how to do several interesting things with Clojure.
My test setup
I like to keep my tests separate from the code it is testing. In the root directory of my project I have a
src directory and test directory. In each directory I have namespaces under com.formpluslogic.budget. For the namespace com.formpluslogic.budget.main the tests will be in com.formpluslogic.budget.test_main.Now that I am organized I'll need an easy way to run all of my tests. I would like to be able to run
bin/test from the root of my project directory and have all of the tests run. To accomplish this I have created two utilities.file_utils contains some functions that help in running tests but may also be useful in other situations.
(ns com.formpluslogic.util.file_utils
(:use [clojure.contrib.str-utils :only (re-split re-gsub)])
(:import (java.io File)))
(defn remove-file-ext [file-name]
(let [index (.lastIndexOf file-name ".")]
(apply str (first (split-at index file-name)))))
(defn file-to-ns-string [f root-dir]
(let [f-sep File/separator
td-p (re-pattern (str f-sep root-dir f-sep))]
(re-gsub (re-pattern f-sep) "."
(remove-file-ext
(last (re-split td-p (.getAbsolutePath f)))))))
(defn file-seq-map-filter [dir mf ff]
(filter ff (map mf (file-seq (File. dir)))))
run_tests will run all of the tests that are found under the test directory.
(ns com.formpluslogic.util.run_tests
(:use [clojure.test :only (run-tests)]
[com.formpluslogic.util.file_utils]))
(def test-dir "test")
(def test-file-prefix "test_")
(def test-file-ext ".clj")
(defn- is-test-file? [file-info]
(let [file-name (first file-info)
re-s (str test-file-prefix ".*" test-file-ext)
re-p (re-pattern re-s)]
(re-matches re-p file-name)))
(defn- get-file-info [f]
[(.getName f) (file-to-ns-string f test-dir)])
(def test-namespaces
(map #(symbol (last %))
(file-seq-map-filter test-dir
get-file-info
is-test-file?)))
(defn run
"Runs all defined tests"
[]
(println "Loading tests...")
(apply require :reload-all test-namespaces)
(time (apply run-tests test-namespaces)))
(run)
I started with code that I found in
clojure.contrib and made a couple of modifications. The run function should be called from the root directory of the project. It will search the test directory for test namespaces and run them. All I need to do to add new tests is create new test files that start with "test_".Finally I created a script in the
bin directory named test that will run the run_tests file.Now that I have an easy, low maintenance way to run my tests, I can start writing them.
Testing private functions
The first function that I chose to test was private. So how do you test private functions? After some research I found some possible solutions on the Clojure group. I went with the following approach.
Here is the private function which is located in the
com.formpluslogic.budget.derby namespace.
(defn- split-criteria
"Create a sequence where the first element is the vector
of keys and the remaining elements are the values"
[criteria]
(cons (vec (keys criteria)) (vals criteria)))
My tests are in
com.formpluslogic.budget.test_derby which is why I cannot directly access the private function. I use ns-resolve to map the function to a var in the com.formpluslogic.budget.test_derby namespace.
(def split-criteria
(ns-resolve 'com.formpluslogic.budget.derby
'split-criteria))
(deftest test-split-criteria
(is (= (split-criteria {:id 1}) (list [:id] 1)))
(is (= (split-criteria {:id 1 :name "John"})
(list [:id :name] 1 "John"))))
What do you think about this? Is there are better way?
Mocking the database and testing printed output
This application uses a database to store information on disk. When testing, I want to be able to run my tests without using the rdms that I am using for functional and integration testing. I also want to make it easy to use a different database implementation. At the moment I am using Apache Derby. In my next post I will show how I set this up as well as how to test printed output.

Thanks for the tip on using (ns-resolve ...) to get access to privates. It seems like maybe there should be a flag for (use 'my.ns) to refer to private functions also, so this isn't so annoying.
ReplyDelete