tests are procedures, test-modules are r6rs-libraries, mostly functional, no hidden state variables, no goops objects, no opaque data-types,
all assert bindings accept an optional title/name string as the first argument, which is eventually displayed and stored in the result. on failure, the titles of nested asserts get combined to create the name of the test.
(assert-true (= 3 (+ 2 1))) (assert-true "addition-test" (= 3 (+ 2 1)))
(assert-equal "addition-test" (+ 1 2) 3) (assert-equal (+ 1 2) 3)
(assert-and "groupname" (assert-true "addition-test" (= 3 (+ 2 1))) (assert-equal "another addition-test" (= 6 (+ 5 4))) (assert-true (+ 2 1)))
(define-test-module (test module sph string) (import (sph string)) (test-execute-procedures-lambda (string-indices ("/a////b//c/d" "/") (0 2 3 4 5 7 8 10) ("/a////b//c/d" "//") (2 4 7) ("abcd" "bc") (1) ("abcdbcefbcg" "bc") (1 4 8) ("abcd" "cd") (2) ("ab" "") (0 1 2) ("" "") (0) ("" "ab") () ("abcd" "cb") ()) (string-multiply ("a" 0) "" ("a" 3) "aaa" ("" 3) "") (string-replace-string ("/a////b//c/d" "//" "/") "/a//b/c/d" ("abcd" "bc" "") "ad" ("abcdbcefbcg" "bc" "hij") "ahijdhijefhijg" ("abcd" "abc" "e") "ed" ("abcd" "cd" "efg") "abefg" ("ab" "" "") "ab" ("ab" "" "/") "/a/b/" ("" "" "/") "/" ("" "" "") "")))
test-execute-procedures-lambda ist a helper for defining basic input/output tests.
string-indices 1 2 3 4 5 6 7 8 9 string-multiply 1 2 3 string-replace-string 1 2 3 4 5 6 7 8 9
string-indices 1 2 3 4 5 6 7 8 9 string-multiply 1 2 3 string-replace-string 1 2 3 4 5 6 7 8 9 string-quote 1 failure string-quote 2 inp "t'est" exp "\"t'est\"x" out "\"t'est\""
test module sph vector vector-append 1 vector-produce 1 vector-range-ref 1 2 3 vector-select 1 test module sph record define-record 1 record 1 alist->record 1 record-field-names 1
test module custom-test-module-files ... helper custom-test-helper-module-files ...
(define-test-module (symbol ...) (import r6rs-library-import-spec ...) any ... procedure:{settings:list -> boolean/vector:test-result})
the following libraries are implicitly imported: (guile) (rnrs base) (sph) (sph test). the last expression of a test-module must be a procedure. this procedure will be executed to get the test result for the module.
(define-test-module (test module mymodule) (import (mymodule)) (lambda (settings) ;create a test-result, a boolean would do it, but the following creates a test-result record with more information (assert-true "addition-test" (= 3 (+ 2 1)))))
the rest of the define-test-module body is exactly like the body of a r6rs-library definition. another example of how a module can look like:
(define-test-module (test module my-module) (import (my-module)) (define (my-utility a b) (+ a b)) (define-test (addition) (assert-true "addition-test" (= 3 (+ 2 1)))) (define-test (addition-2 arguments) (apply + arguments)) (test-execute-procedures-lambda addition (addition-2 (5 4) 9) (+ (1 2) 3 (3 4) 7)))
test-results in a test-modules execute procedure can be created in any way scheme permits. (sph test) offers a few additional bindings for that. they all use a specific notation for tests to be run, the test arguments and expected-results, called "test-spec".
symbol/(symbol [any/(any ...) ...])
procedure-name/(procedure-name [arguments-and-expected-result-alternatingly ...])
(string-quote "'test'" "\"'test'\"") (string-multiply ("" 3) "")
can be used as the last expression in a test-module definition. creates a anonymous procedure that takes and uses a settings argument and evaluates test-specs in an implicit quasiquote.
(test-execute-procedures-lambda (my-procedure (unquote (+ 1 2)) 3))
define a variable with a list of test-specs like test-execute-procedures-lambda
runs tests test procedures are not required to return test-result records, any other value is compared against the specified expected output
"test-execute-procedures" and similar bindings look for custom test procedures with certain type signatures. the following syntax can be used to define such procedures.
(define-test (name argument-name ...) test-result)
(define-test name procedure) (define-test (abc) #t) (define-test (abc arguments-list) #t) (define-test (abc arguments-list expected-result) #t) (define-test (abc name index arguments-list expected-result) #t)
both or none of the two name/index arguments need to be specified.. internally, test procedures are top-level bindings with a name that is prefixed with "test-".
test-{name} :: [name index] arguments expected-result -> any/vector/boolean:test-result
(define (test-abc) #t) (define (test-abc arguments-list) #t) (define (test-abc arguments-list expected-result) #t) (define (test-abc name index arguments-list expected-result) #t)
test-result: ([group-name] test-result ...)/test-result-record
#!/usr/bin/guile !# (import (sph) (sph test)) (define settings (test-settings-default-custom path-search "modules" reporter-name (q compact))) (test-execute-modules-by-prefix #:settings settings (q (test module sph)))
(test-create-result [boolean:success? string:title integer:index any:result list:arguments any:expected])
following is an example of a test-module execute procedure for updating settings and installing a hook that runs before each new procedure test
(define-test-module (test module example) (import (sph test) (sph alist) (sph storage dg) (test helper sph storage dg)) (define-procedure-tests tests (dg-create-ide) (dg-aliases) (dg-ide->path 123456 "files/1e240" 789 "files/315")) (lambda (settings) (let (settings (alist-q-merge-key/value settings random-order? #t hook (alist-q-merge-key/value (alist-q-ref settings hook) procedure-before (lambda a (test-env-dg-reset) (if (not (dg-index-no-errors? (or (dg-index-errors-intern) (dg-index-errors-pair)))) (throw (q index-corruption))))))) (test-execute-procedures settings tests))))
given the recommended directory structure for tests, one can create standard r6rs libraries in files stored in a load-path under test/helper/ and import them in test-modules
currently it is fully up to the user to create and destroy eventually necessary environments for tests. for example database initialisation with test data. given the hook system of (sph test), it should be possible to create nice abstractions for that.
settings may apply to procedure, module and modules tests. the following examples use the "alist-q" binding from (sph alist), which creates an alist from an alternated key/value specification where keys are quoted. "define-as" is also used, which is the same as "(define test-settings-default (alist-q _ ...))".
(define-as test-settings-default alist-q reporters test-reporters-default reporter-name (q default) search-type (q prefix) hook (alist-q procedure-before ignore procedure-after ignore procedure-data-before ignore procedure-data-after ignore module-before ignore module-after ignore modules-before ignore modules-after ignore) random-order? #f parallel? #f exception-strings? #t exclude #f only #f until #f)
if true, exeptions occuring in test-execute-procedures tests are catched and converted to strings that will be the result of the test procedure. this way, tests for if an exception occurs can be made.
list of procedure or module names to exclude from test execution
the association list stored under the "hook" key contains procedures that are called at specific times in the test execution process. the type signatures can be seen in the following scheme comments.
(define-as test-report-hooks-null alist-q ;settings name -> procedure-before ignore ;settings result -> procedure-after ignore ;settings name index data -> procedure-data-before ignore ;settings result -> procedure-data-after ignore ;settings module-name -> module-before ignore ;settings module-name result -> module-after ignore ;settings module-names -> modules-before ignore ;settings module-names result -> modules-after ignore)
the test order is randomised before execution
default reporter names are "compact", "null" and "default" (which is equivalent to "null")
symbol:name -> (procedure:report-result:{test-result [port] ->} . list:hook-configuration)
reporter configuration is a pair that consists of a procedure for reporting a test-result object and a hook-configuration
specifies how to match module names: partially (prefix or prefix-not-exact) and therefore possibly multiple modules, or as exact, full module names
stops when reaching the specified procedure/module
this macro exists for leaving out optional parameters and to avoid the need to manually prepend "test-" to the name.