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
|
A [testiere](https://www.vocabulary.com/dictionary/testiere) is armor
for the head of a horse.
Testiere is a Common Lisp library for putting "protection" (tests) in
the "head" of functions.
# testiere
Embed tests inside function defintions. Tests are run when you compile
a function, signalling errors when they fail. Great for running tests
during interactive development!
A work in progress. But here is the basic idea:
(defun/t sum-3 (x y &key (z 10))
"Sums three numbers, Z has a default value of 10"
:tests
(= (1 2) 13) ; (sum-3 1 2) == 13
(= (1 2 :z 3) 6) ; (sum-3 1 2 :z 3) == 6
(:output (0 0) ; tests that (sum-3 0 0) passes the predicate
(lambda (result) (= 10 result)))
(:fails ; ensures that (sum-3 "strings" "ain't" :z "numbers") fails
("strings" "ain't" :z "numbers"))
:end
(+ x y z))
Here we define a function with four embedded tests. If any of these
tests fail, *the function is not defined*. If the funtion is already
defined, it is not redefined.
## basic tests
The most basic tests look like
(COMPARATOR (ARG1 ARG2 ...) VALUE)
Which will call the function with the provided arguments are compare
it to a VALUE.
For example, `(>= (1 2 :z 1) -5)` runs the test
(assert (>= (sum-3 1 2 :z 1) -5))
## additional tests
In addition to basic assertions on the output of functions, testire
supports more elaborate vocubulary for embedding tests. Most of these look
like:
(KEYWORD (ARG1 ARG2 ...) TERM)
Where `TERM` varies according to the `KEYWORD` supplied.
A few of these are
- `(:program FUNCTION-NAME ARGS...)` runs a funcion named
FUNCTION-NAME with arguments ARGS. This function is meant to act as
a test suite for the function being defined with defun/t. It may
call that function and ASSERT things about it.
- `(:outputp (..ARGS...) PREDICATE)` asserts that the output passes the
one-argument predicate.
- `(:afterp (...ARGS...) THUNK)` asserts that the thunk should return
non-nil after the function has run. Good for testing values of
dynamic variables that the function might interact with.
- `(:fails (...ARGS...))` asserts that the function will produce an
error with the given arguments.
- `(:signals (...ARGS...) CONDITION)` where `CONDITION` is the name of
a condition. Asserts that the function will signal a condition of
the supplied type when called with the provided arguments.
## Stubs and Bindings
Tests can also embed bindings or mock-functions aka stubs.
Binding variables looks like
(:with-bindings LET-BINDINGS TESTS)
and are useful for binding dynamic variables for use during a set of
tests.
For example
(defvar *count*)
(defun/t increment-count ()
"Increments the *count* variable."
:tests
(:with-bindings ((*count* 4))
(:afterp () (lambda () (= *count* 5))) ; 5 after the first call
(= () 6) ; 6 after the second
(:outputp () (lambda (x) (= x 7)))) ; and 7 after the third
:end
(incf *count*))
The `:with-stubs` form is similar, except that it binds temporary
values to functions that might be called by the form in
questions. Useful for mocking.
(defun just-a-function ()
(print "Just a function."))
(defun/t call-just-a-function ()
"Calls JUST-A-FUNCTION."
:tests
(:with-stubs ((just-a-function () (print "TEMP JUST-A-FUNCTION.")))
(equal () "TEMP JUST-A-FUNCTION."))
:end
(just-a-function))
In the above, the temporary redefinition of JUST-A-FUNCTION is used.
## `WITH-STUBS` and `:PROGRAM` Style Tests
By using the `:program` test and the `with-stubs` macro, you can
define just about any kind of test. In the following, you use
`with-stubs` to mock a function that makes an http request and returns
a string.
It is assumed that `make-drakma-request` is defined somewhere else and
performs some network IO. It is stubbed here so that we do not need to
actually access the network.
(defun test-url-word-counter ()
"Stub the function that makes requests that word-counter uses, and then test word counter."
(with-stubs
((make-drakma-request () "one two three four five six seven"))
(assert (= (count-words) 7))))
(defun/t count-words ()
"Fetches a url and counts now many words the page contains."
:tests
(:program test-url-word-counter)
:end
(let ((fetched
(make-drakma-request)))
(1+ (count #\space fetched))))
If `test-url-word-counter` fails, `count-words` isn't defined.
|