aboutsummaryrefslogtreecommitdiff
path: root/README.org
blob: 129303f9cf2591d3a0da30965588fa70c9180980 (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
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
A [[https://en.wiktionary.org/wiki/testiere][testiere]] is armor for the head of a horse and ~testiere~ is armor
for the head of your ~defun~ forms.  

* Testiere

With ~testiere~ you can program in an interactive TDD-like
fashion. Tests are included at the top of a ~defun/t~ form. When you
recompile your functions interactively, the tests are run. If any
fail, you are dropped into a debugger where you can decide to revert
the definition to the last known working version, or you can choose to
unbind it altogether.

The system supports mocking and stubbing in your tests, so that you
can, e.g. test the system in different dynamic contexts or by mocking
network request functions.

Here is an example:

#+begin_src lisp

(defun/t sum-3 (x y &key (z 10))
  "Sums three numbers, Z has a default value of 10"
  :tests
  (:program some-test-function)
  (= (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))

#+end_src

In the above, a function ~sum-3~ is defined with five embedded
tests. The test specification syntax is detailed below. If any of the
tests fail, the function will not be redefined and you will drop into
the debugger, which asks you how you'd like to proceed.

The approach to TDD-like development taking by ~testiere~ may not be
appropriate to all circumstances, but it is good for interactive
development of interactive applications (😉) whose "main loop"
involves a good sized collection of unit-testable functions.

** Test Specification 

There are a few kinds of tests available. 

*** For the Impatient, Just Use =:program= Tests

Most users will probably benefit from the ~:program~ style test.  Here
is a quick example:

#+begin_src lisp

(defun test-fibble ()
  (assert (= 13 (fibble 1 2))))

(defun/t fibble (x y &key (z 10))
  "Adds three numbers, one of which defaults to 10."
  :tests
  (:program test-fibble)
  :end
  (+ x y z))

#+end_src

In the above test, we insist that the ~test-fibble~ function not
signal an error condition in order for ~fibble~ to be successfully
(re)compiled.

*** Basic Test Specifications

A test suite is a list of forms that appear between ~:tests~ and
~:end~ in the body of a ~defun/t~ form.  The test suite must appear
after any optional docstring and before the function body actually
begins.

A catalog of test form specifications follows.

**** Comparator Test Specifications

: (comparator (&rest args...) value)

The ~comparator~ should be the name of a binary predicate (like ~<~ or
~eql~).  These tests proceed by calling ~(comparator (apply my-fun args) value)~
If the comparison fails, an error condition is signaled. 

Amending the above example, we include a comparator test:


#+begin_src lisp
(defun/t fibble (x y &key (z 10))
  "Adds three numbers, one of which defaults to 10."
  :tests
  (:program test-fibble)
  (= (0 0 :z 39) 30)     ; (assert (= (fibble 0 0 :z 30) 30))
  :end
  (+ x y z))

#+end_src

**** Other Test Specifications

Every other form appearing in a test suite is a list that starts with
a keyword. 

- ~(:program FUNCTION-NAME ARGS...)~ runs a function 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.


*** Mocking and Stubbing 

The following test forms allow for the running of tests inside a
context in which certain functions or global values are bound:

Binding variables looks like

: (:let LET-BINDINGS TESTS) 
    
and are useful for binding dynamic variables for use during a set of
tests.

For example 

#+begin_src lisp

    (defvar *count*)
    
    (defun/t increment-count ()
      "Increments the *count* variable."
      :tests
      (:let ((*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*))
#+end_src
    
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.

#+begin_src lisp


    (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))

#+end_src
      
In the above, the temporary redefinition of ~JUST-A-FUNCTION~ is used.