If functions are horses, testiere is their armor.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
Colin Okay 6990d42fac demethod+ not implemented 1 month ago
LICENSE added license 3 months ago
README.md phrasing 1 month ago
examples.lisp added examples.lisp 1 month ago
package.lisp exporting defining forms. 4 months ago
testiere.asd initial commit, defun+ initial definitino 4 months ago
testiere.lisp demethod+ not implemented 1 month ago

README.md

A 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+ fibble (x y &key (z 10))
  "Hey, a docstring."
  :tests
  (= (1 2) 13)
  (>= (1 2 :z 1) -5)
  (:outputp (0 0 :z 0) (lambda (result) (equalp result 0)))
  (: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 (>= (fibble 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

  • (: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+ 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+ 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.