2 Marco Baringer <mb@bese.it>
8 :website: http://common-lisp.net/project/fiveam
9 :stylesheet: fiveam.css
14 Before we even start, we'll need to load FiveAM itself:
16 --------------------------------
17 CL-USER> (quicklisp:quickload :fiveam)
24 CL-USER> (use-package :5am)
26 --------------------------------
28 == Failure For Beginners ==
30 Now, this is a tutorial to the testing framework FiveAM. Over the
31 course of this tutorial we're going to test an implementation of
32 link:https://en.wikipedia.org/wiki/Peano_axioms[peano numbers]
33 (basically, pretend that lisp didn't have integers or arithmetic built
34 in and we wanted to add it in the least efficent way possible). The
35 first thing we need is the constant `0`, a function `zero-p` for
36 testing if a number is zero, and function `succ` which, given a number
37 `N`, returns its successor (in other words `N + 1`).
39 It's still not totally clear to me what the `succ` function should
40 look like, but the `zero` and `zero-p` functions are easy enough, so
41 let's define a test for those two funtions. We'll start by testing
42 `zero` as much as we can:
44 --------------------------------
47 --------------------------------
50 ignore the second argument to def-test for now. if it helps pretend it's filler to make the identation look better.
52 Since we don't know, nor really care at this stage, what the function
53 `zero` returns, we simply use the
54 link:manual.html#FUNCTION_FINISHES[`FINISHES`] macro to make sure that
55 the function does in fact return (as opposed to signaling some weird
56 error). Our `zero-p` test, on the other hand, does actually have
57 something we can test. Whatever is returned by `zero` should be
60 --------------------------------
62 (is-true (zero-p (zero))))
63 --------------------------------
65 Finally, let's run our tests:
67 --------------------------------
76 --------------------------------
78 Unexpected Error: #<UNDEFINED-FUNCTION ZERO {10058AD6F3}>
79 The function COMMON-LISP-USER::ZERO is undefined..
80 --------------------------------
81 --------------------------------
83 Unexpected Error: #<UNDEFINED-FUNCTION ZERO {10056FE5A3}>
84 The function COMMON-LISP-USER::ZERO is undefined..
85 --------------------------------
87 --------------------------------
89 so, 100% failure rate, and even an Unexpected error...that's bad, but
90 it's also what we should have been expecting given that we haven't
91 actually defined `zero-p` or `zero`. So, let's define those two
94 --------------------------------
95 CL-USER> (defun zero () 'zero)
97 CL-USER> (defun zero-p (value) (eql 'zero value))
99 --------------------------------
101 Now let's run our test again:
103 --------------------------------
110 --------------------------------
117 There's actually a bit of work being done with suites and default
118 tests and stuff in order to make that `run!` call do what it just did
119 (call our previously defined tests). If you never create a suite on
120 your own then you can think of `run!` as being the 'run every test'
121 function, if you start creating your own suites (and you will
122 eventually), then you'll want to know that run's second, optional,
123 argument is the name of a test or suite to run, but until then just go
128 So, we have zero, and we can test for zero ness, wouldn't it be nice
129 to have the number one too? How about the number two? how about a
130 billion? I like the number 1 billion. Now, since we thoroughly read
131 through the wiki page on peano numbers we now that there's a function,
132 called `succ` which, give one number returns the "next" one. In this
133 implementation we're going to represent numbers as nested lists, so
134 our `succ` function just wraps its input in another cons cell:
136 --------------------------------
139 --------------------------------
141 Easy enough. That could also be right, it could also be wrong too, we
142 don't really have a way to check (yet). We do know one thing though,
143 the `succ` of any number (even zero) isn't zero. So let's redefine our
144 zero test to check that zero plus one isn't zero:
146 --------------------------------
148 (is-true (zero-p (zero)))
149 (is-false (zero-p (succ (zero)))))
150 --------------------------------
152 and let's run the test:
154 --------------------------------
161 --------------------------------
165 == Elementary, my dear watson. Run the test. ==
167 When working interactively like this, we almost always define a
168 test and then immediately run it, we can tell fiveam to do that
169 automatically by setting `*run-test-when-defined*` to T:
171 --------------------------------
172 CL-USER> (setf *run-test-when-defined* t)
174 --------------------------------
176 Now if we were to redefine (either via the repl as I'm doing here or
177 via C-cC-c in a slime buffer), we'll see:
179 --------------------------------
180 CL-USER> (def-test zero-p ()
181 (is-true (zero-p (zero)))
182 (is-false (zero-p (plus-one (zero)))))
189 --------------------------------
191 Great, at this point it's time we add a function for testing integer
192 equality (in other words, `cl:=`). Let's try with this:
194 --------------------------------
195 CL-USER> (defun equiv (a b)
196 (and (zero-p a) (zero-p b)))
198 --------------------------------
201 Since i'm doing everything in the package common-lisp-user i
202 couldn't use the name `=` (or even `equal`). I don't want to talk
203 about packages at this point, so we'll just have to live with `equiv`
208 --------------------------------
209 CL-USER> (def-test equiv () (equiv (zero) (zero)))
210 Didn't run anything...huh?
212 --------------------------------
214 Well, that's not what I was expecting. I'd forgotten that FiveAM,
215 unlike other test frameworks, doesn't actually look at the return
216 value of the function, it only runs its so called checks (one of which
217 is the `is-true` function we've been using so far). So let's add that
220 --------------------------------
221 CL-USER> (def-test equiv ()
222 (is-true (equiv (zero) (zero))))
230 --------------------------------
232 == Failing, but gently. ==
234 Nice, now, finally, we can test that 1 is equal to 1 (or, in our
235 implementation, the successor of zero is equal to the successor of
238 --------------------------------
239 CL-USER> (def-test equiv ()
240 (is-true (equiv (zero) (zero)))
241 (is-true (equiv (succ (zero)) (succ (zero)))))
249 --------------------------------
251 (EQUIV (SUCC (ZERO)) (SUCC (ZERO))) did not return a true value
252 --------------------------------
255 --------------------------------
257 Oh, cry, baby cry. The important part of that output is this line:
259 --------------------------------
261 (EQUIV (SUCC (ZERO)) (SUCC (ZERO))) did not return a true value
262 --------------------------------
264 That means that, in the test `EQUIV` the form `(EQUIV (SUCC (ZERO))
265 (SUCC (ZERO)))` evaluated to NIL. I wonder why? It'd be nice to see
266 what the values evaluated to, what the actual arguments and return
267 value of `EQUIV` was. There are two things we could do at this point:
269 . Set 5am:*debug-on-failure* to `T` and re-run the test and dig around
270 in the backtrace for the info we need.
272 . Use the `IS` check macro to get a more informative message in the
275 In practice you'll end up using a combination of both (though i prefer
276 that tests run to completion without hitting the debugger, and this
277 may have influenced fiveam a bit, but others prefer working with live
278 data in a debugger window and that's an equally valid approach).
280 == Tell me what I need to know ==
282 However, since this a non-interactive static file, and debuggers are
283 really interactive and implementation specific, I'm going to go with
284 the second option for now, here's the same test using the `IS` check
285 instead of `IS-TRUE`:
287 --------------------------------
288 CL-USER> (def-test equiv ()
289 (is (equiv (zero) (zero)))
290 (is (equiv (succ (zero)) (succ (zero)))))
298 --------------------------------
315 --------------------------------
319 <1> actual value's source code
320 <2> actual value's value
321 <3> comparison operator
323 --------------------------------
325 I need to mention something at this point: the `IS-TRUE` and `IS`
326 macro do not do anything different at run time. They both have some
327 code, which they run, and if the result is NIL they record a failure
328 and if not they record a success (which 5am calls a pass). The only
329 difference is in how they report a failure: The `IS-TRUE` function
330 just stores the source form and prints that back, the `IS` macro
331 assumes that the form has a specific format:
333 (TEST-FUNCTION EXPECTED-VALUE ACTUAL-VALUE)
335 and generates a failure message based on that. In this case we
336 evaluated `(succ (zero))`, and got `(zero)`, and passed this value,
337 along with the result of the expected value (`(succ (zero))`) to
338 `equiv` and got `NIL`.
340 Now, back to our test, it's actually pretty obvious that our current
341 implementation of equiv:
343 --------------------------------
345 (and (zero-p a) (zero-p b)))
346 --------------------------------
348 is buggy, so let's fix and run the test again:
350 --------------------------------
351 CL-USER> (defun equiv (a b)
352 (if (and (zero-p a) (zero-p b))
354 (equiv (car a) (car b))))
364 --------------------------------
366 == Again, from the top ==
368 Great, our tests passed. You'll notice though that this time we used
369 the `!` function instead of `run!`.
371 == Birds of a feather flock together. Horses of a different color stay home. ==
373 So far we've always defined and run single tests, while it's certainly
374 possible to continue this way it gets unweidly pretty quickly.