aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: 3f27f8748fc6f0648907a37398bafb6ece0b1264 (plain)
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.