1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
|
;;; test_is.clj: test framework for Clojure
;; by Stuart Sierra, http://stuartsierra.com/
;; January 16, 2009
;; Thanks to Chas Emerick, Allen Rohner, and Stuart Halloway for
;; contributions and suggestions.
;; Copyright (c) Stuart Sierra, 2008. All rights reserved. The use
;; and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution. By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license. You must not
;; remove this notice, or any other, from this software.
(comment
;; Inspired by many Common Lisp test frameworks and clojure/test,
;; this file is a Clojure test framework.
;;
;; Define tests as :test metadata on your fns. Use the "is" macro
;; for assertions. Examples:
(defn add2
([x] (+ x 2))
{:test (fn [] (is (= (add2 3) 5))
(is (= (add2 -4) -2)
(is (> (add2 50) 50))))})
;; You can also define tests in isolation with the "deftest" macro:
(deftest test-new-fn
(is (= (new-fn) "Awesome")))
;; You can test that a function throws an exception with the
;; "is thrown?" form:
(defn factorial
([n] (cond
(zero? n) 1 ; 0!=1 is often defined for convenience
(> n 0) (* n (factorial (dec n)))
:else (throw (IllegalArgumentException. "Negative factorial"))))
{:test (fn [] (is (= (factorial 3) 6))
(is (= (factorial 6) 720))
(is (thrown? IllegalArgumentException (factorial -2))))})
;; Run tests with (run-tests). As in any language with macros, you
;; may need to recompile functions after changing a macro
;; definition.
;;
;; If you want write a bunch of tests with the same predicate, use
;; "are", which takes a template and applies it inside "is".
;;
;; Examples:
(deftest test-addition
(are (= _1 _2)
3 (+ 2 1)
4 (+ 2 2)
5 (+ 4 1)))
(deftest test-predicates
(are _ ;; the template is just an underscore
(true? true)
(false? false)
(nil? nil)))
) ;; end comment block
(ns clojure.contrib.test-is
(:require [clojure.contrib.template :as temp]
[clojure.contrib.stacktrace :as stack]))
(defonce
#^{:doc "True by default. If set to false, no test functions will
be created by deftest, set-test, or with-test. Use this to omit
tests when compiling or loading production code."}
*load-tests* true)
;;; PRIVATE GLOBALS
(def *report-counters* nil) ; bound to a ref of a map in test-ns
(def *testing-vars* (list)) ; bound to hierarchy of vars being tested
(def *testing-contexts* (list)) ; bound to strings of test contexts
(def *initial-report-counters* ; used to initialize *report-counters*
{:test 0, :pass 0, :fail 0, :error 0})
;;; UTILITIES FOR REPORTING FUNCTIONS
(defn file-position
"Returns a vector [filename line-number] for the nth call up the
stack."
[n]
(let [s (nth (.getStackTrace (new java.lang.Throwable)) n)]
[(.getFileName s) (.getLineNumber s)]))
(defn testing-vars-str
"Returns a string representation of the current test. Renders names
in *testing-vars* as a list, then the source file and line of
current assertion."
[]
(let [[file line] (file-position 4)]
(str
;; Uncomment to include namespace in failure report:
;;(ns-name (:ns (meta (first *testing-vars*)))) "/ "
(reverse (map #(:name (meta %)) *testing-vars*))
" (" file ":" line ")")))
(defn testing-contexts-str
"Returns a string representation of the current test context. Joins
strings in *testing-contexts* with spaces."
[]
(apply str (interpose " " (reverse *testing-contexts*))))
(defn inc-report-counter
"Increments the named counter in *report-counters*, a ref to a map.
Does nothing if *report-counters* is nil."
[name]
(when *report-counters*
(dosync (commute *report-counters* assoc name
(inc (or (*report-counters* name) 0))))))
;;; TEST RESULT REPORTING
(defmulti
#^{:doc "Handles the result of a single assertion. 'event' is one
of :pass, :fail, or :error. 'msg' is a comment string associated
with the assertion. 'expected' and 'actual' are quoted forms,
which will be rendered with pr-str.
Special case: if 'event' is :info, just the 'msg' will be
printed.
You can rebind this function during testing to plug in your own
test-reporting framework."}
report (fn [event msg expected actual] event))
(defmethod report :info [event msg expected actual]
(newline)
(println msg))
(defmethod report :pass [event msg expected actual]
(inc-report-counter :pass))
(defmethod report :fail [event msg expected actual]
(inc-report-counter :fail)
(println "\nFAIL in" (testing-vars-str))
(when (seq *testing-contexts*) (println (testing-contexts-str)))
(when msg (println msg))
(println "expected:" (pr-str expected))
(println " actual:" (pr-str actual)))
(defmethod report :error [event msg expected actual]
(inc-report-counter :error)
(println "\nERROR in" (testing-vars-str))
(when (seq *testing-contexts*) (println (testing-contexts-str)))
(when msg (println msg))
(println "expected:" (pr-str expected))
(print " actual: ")
(if (instance? Throwable actual)
(stack/print-cause-trace actual 5)
(prn actual)))
;;; UTILITIES FOR ASSERTIONS
(defn get-possibly-unbound-var
"Like var-get but returns nil if the var is unbound."
[v]
(try (var-get v)
(catch IllegalStateException e
nil)))
(defn function?
"Returns true if argument is a function or a symbol that resolves to
a function (not a macro)."
[x]
(if (symbol? x)
(when-let [v (resolve x)]
(when-let [value (get-possibly-unbound-var v)]
(and (fn? value)
(not (:macro (meta v))))))
(fn? x)))
(defn assert-predicate
"Returns generic assertion code for any functional predicate. The
'expected' argument to 'report' will contains the original form, the
'actual' argument will contain the form with all its sub-forms
evaluated. If the predicate returns false, the 'actual' form will
be wrapped in (not...)."
[msg form]
(let [args (rest form)
pred (first form)]
`(let [values# (list ~@args)
result# (apply ~pred values#)]
(if result#
(report :pass ~msg '~form (cons ~pred values#))
(report :fail ~msg '~form (list '~'not (cons '~pred values#))))
result#)))
(defn assert-any
"Returns generic assertion code for any test, including macros, Java
method calls, or isolated symbols."
[msg form]
`(let [value# ~form]
(if value#
(report :pass ~msg '~form value#)
(report :fail ~msg '~form value#))
value#))
;;; ASSERTION METHODS
;; You don't call these, but you can add methods to extend the 'is'
;; macro. These define different kinds of tests, based on the first
;; symbol in the test expression.
(defmulti assert-expr
(fn [msg form]
(cond
(nil? form) :always-fail
(seq? form) (first form)
:else :default)))
(defmethod assert-expr :always-fail [msg form]
;; nil test: always fail
`(report :fail ~msg nil nil))
(defmethod assert-expr :default [msg form]
(if (and (sequential? form) (function? (first form)))
(assert-predicate msg form)
(assert-any msg form)))
(defmethod assert-expr 'instance? [msg form]
;; Test if x is an instance of y.
`(let [klass# ~(nth form 1)
object# ~(nth form 2)]
(let [result# (instance? klass# object#)]
(if result#
(report :pass ~msg '~form (class object#))
(report :fail ~msg '~form (class object#)))
result#)))
(defmethod assert-expr 'thrown? [msg form]
;; (is (thrown? c expr))
;; Asserts that evaluating expr throws an exception of class c.
;; Returns the exception thrown.
(let [klass (second form)
body (rrest form)]
`(try ~@body
(report :fail ~msg '~form nil)
(catch ~klass e#
(report :pass ~msg '~form e#)
e#))))
;; New assertions coming soon:
;; * thrown-with-msg?
;;; CATCHING UNEXPECTED EXCEPTIONS
(defmacro try-expr
"Used by the 'is' macro to catch unexpected exceptions.
You don't call this."
[msg form]
`(try ~(assert-expr msg form)
(catch Throwable t#
(report :error ~msg '~form t#))))
;;; ASSERTION MACROS
;; you use these in your tests
(defmacro is
"Generic assertion macro. 'form' is any predicate test.
'msg' is an optional message to attach to the assertion.
Example: (is (= 4 (+ 2 2)) \"Two plus two should be 4\")
Special form (is (thrown? c body)) checks that an instance of c is
thrown from body, fails if not; then returns the thing thrown."
([form] `(is ~form nil))
([form msg] `(try-expr ~msg ~form)))
(defmacro are
"Experimental. May be removed in the future.
|