;; "Simple Unit Test for Clojure" - {@Author: Kunjan @Date: 8/20/2009}

;; ----------------------------------------------------------------------------

( ns unittest )

( defmacro is [ expr & [ value ] ]

"is : checks if the expression is the same as the value

is : s-expression value -> (U boolean (listof lists))

is a a macro because we need to print the expression itself first"

` ( let [ evaluated# ~expr ]

( if ( or ( = evaluated# true ) ( = evaluated# ~ value ) )

true

( list ( list "

In: \t \t " ( str ( quote ~expr ) ) )

( list "

U Expected: \t "

( if ( nil ? ~ value ) ( not evaluated# ) ~ value ) )

( list "

I Got: \t " evaluated# ) ) ) ) )

;; ----------------------------------------------------------------------------

( defn present?

"predicate checks if the value is present in the sequence

present? : T (listof T) -> boolean

Structural recursion over a sequence. Clojure doesn't have TCO, we use recur"

[ value a-sequence ]

( cond

( empty? a-sequence ) false

: else

( if ( = value ( first a-sequence ) )

true

( recur value ( rest a-sequence ) ) ) ) )

( defn perform-tests

"performs the tests in the test bundle

optional-para is needed to see if the tests need to halt on error

strategy: lazy evaluation over a list of tests"

[ test-results optional-para count ]

( cond

( empty? test-results ) ( list "

---- Finished Checking -----

" )

: else

( if ( = true ( first test-results ) )

( perform-tests ( rest test-results ) optional-para ( inc count ) )

( cons

( list ( list "

FAILED Test #" count ) ( first test-results ) )

( if ( present? : runall optional-para )

( perform-tests ( rest test-results ) optional-para ( inc count ) ) ) ) ) ) )

;; =============================== MAIN FUNCTION ===============================

( defn define-test-bundle

"

Purpose: performs the series of test.

Header : (test-bundle test-name [optional-para] (is test1) (is test2)...)

Example: (test-bundle 'my-test [:runall :time :noprint]

(is (= 2 3) true)(is (= 2 2) true))

Note: testname is compulsory

optional-para could be a vector containing these keywords:

:runall - if provided it does not halt on the first error

Default: Stops on the first error

:time - if provided profiles the running time

:noprint- if provided returns a list of the parsable s-expression results

:skip - if provided skips the current test bundle.

0.is-expr are the series of test expressions

1.Write a test bundle for every test

2.Halt on the first error so that you can debug them one by one

3.Then profile it

4.Finally Skip the bundle once all tests pass.

5. When the Module is complete,remove all the skip instructions for integrations checks

6.If you are building any automated builders, you can use the parsable s-expressions"

[ testname & [ optional-para & is-expr ] ]

( when- not ( present? : skip optional-para )

( do

( if ( present? : time optional-para )

( def result ( cons testname ( time ( perform-tests is-expr optional-para 1 ) ) ) )

( def result ( cons testname ( perform-tests is-expr optional-para 1 ) ) ) )

( if ( present? : noprint optional-para )

result

( print ( flatten result ) ) ) ) ) )

;; =============================== SAMPLE TEST ===============================

( define-test-bundle 'my-test [ : runall : time ]

;; Simple False Statement

( is ( = 2 3 ) )

;; Simple True Statement

( is ( + 2 2 ) 4 )

;; Simple Predicate Checks

( is ( instance? clojure . lang . Symbol 'alphabet ) )

;; Compare Data Structures

( is ( map ( fn [ x ] ( Math/ sqrt x ) ) ( list 4 16 ) ) ( list 2 5 ) )

( is { : key 1 } { : key 2 } )

;; Compare Newer Bindings

( is ( let [ x 2 ] ( * x x ) ) 5 )

;; Compare Strings

( is ( . toUpperCase "foo" ) "FOo" )

;; Symbol Checks

( is 'abc ( symbol "abc" ) ) )

;; =================== RESULT FROM CONSOLE ===============================

;; 1:1 user=> "Elapsed time: 0.13386 msecs"

;; (my-test

;; FAILED Test # 1

;; In: (= 2 3)

;; U Expected: true

;; I Got: false

;; FAILED Test # 4

;; In: (map (fn [x] (Math/sqrt x)) (list 4 16))

;; U Expected: 2 5

;; I Got: 2.0 4.0

;; FAILED Test # 5

;; In: {:key 1}

;; U Expected: {:key 2}

;; I Got: {:key 1}

;; FAILED Test # 6

;; In: (let [x 2] (* x x))

;; U Expected: 5

;; I Got: 4

;; FAILED Test # 7

;; In: (.toUpperCase "foo")

;; U Expected: FOo

;; I Got: FOO

;; ---- Finished Checking -----