separated "internals & extension API" from main "RackUnit API"

This commit is contained in:
Ryan Culpepper 2010-09-01 15:47:52 -06:00
parent c5e6580f02
commit 3097bb85b7
7 changed files with 276 additions and 258 deletions

View File

@ -12,6 +12,3 @@
@include-section["control-flow.scrbl"]
@include-section["misc.scrbl"]
@include-section["ui.scrbl"]
@include-section["running-tests.scrbl"]

View File

@ -372,27 +372,3 @@ The @racket[fail-check] macro raises an @racket[exn:test:check] with
the contents of the check information stack.
}
@section{The Check Evaluation Context}
The semantics of checks are determined by the parameters
@racket[current-check-around] and
@racket[current-check-handler]. Other testing form such as
@racket[test-begin] and @racket[test-suite] change the value
of these parameters.
@defparam[current-check-handler handler (-> any/c any/c)]{
Parameter containing the function that handles exceptions
raised by check failures. The default value is @racket[raise]. }
@defparam[current-check-around check (-> thunk any/c)]{
Parameter containing the function that handles the execution
of checks. The default value wraps the evaluation of
@racket[thunk] in a @racket[with-handlers] call that calls
@racket[current-check-handler] if an exception is raised and then
(when an exception is not raised) discards the result, returning
@racket[(void)].
}

View File

@ -178,38 +178,3 @@ As far I know no-one uses this macro, so it might disappear
in future versions of RackUnit.}
}
@section{Compound Testing Evaluation Context}
Just like with checks, there are several parameters that
control the semantics of compound testing forms.
@defparam[current-test-name name (or/c string? false/c)]{
This parameter stores the name of the current test case. A
value of @racket[#f] indicates a test case with no name,
such as one constructed by @racket[test-begin]. }
@defparam[current-test-case-around handler (-> (-> any/c) any/c)]{
This parameter handles evaluation of test cases. The value
of the parameter is a function that is passed a thunk (a
function of no arguments). The function, when applied,
evaluates the expressions within a test case. The default
value of the @racket[current-test-case-around] parameters
evaluates the thunk in a context that catches exceptions and
prints an appropriate message indicating test case failure.}
@defproc[(test-suite-test-case-around [thunk (-> any/c)]) any/c]{
The @racket[current-test-case-around] parameter is
parameterized to this value within the scope of a
@racket[test-suite]. This function creates a test case
structure instead of immediately evaluating the thunk.}
@defproc[(test-suite-check-around [thunk (-> any/c)]) any/c]{
The @racket[current-check-around] parameter is parameterized
to this value within the scope of a @racket[test-suite].
This function creates a test case structure instead of
immediately evaluating a check.}

View File

@ -0,0 +1,271 @@
#lang scribble/doc
@(require "base.rkt")
@declare-exporting[rackunit #:use-sources (rackunit)]
@title[#:tag "internals"]{RackUnit Internals and Extension API}
This section describes RackUnit's facilities for customizing the
behavior of checks and tests and for creating new kinds of test
runners.
@section{Customizing Check Evaluation}
The semantics of checks are determined by the parameters
@racket[current-check-around] and
@racket[current-check-handler]. Other testing form such as
@racket[test-begin] and @racket[test-suite] change the value
of these parameters.
@defparam[current-check-handler handler (-> any/c any)]{
Parameter containing the function that handles exceptions
raised by check failures. The default value is @racket[raise].
}
@defparam[current-check-around check (-> (-> any) any)]{
Parameter containing the function that handles the execution
of checks. The default value wraps the evaluation of
@racket[thunk] in a @racket[with-handlers] call that calls
@racket[current-check-handler] if an exception is raised and then
(when an exception is not raised) discards the result, returning
@racket[(void)].
}
@section{Customizing Test Evaluation}
Just like with checks, there are several parameters that
control the semantics of compound testing forms.
@defparam[current-test-name name (or/c string? false/c)]{
This parameter stores the name of the current test case. A
value of @racket[#f] indicates a test case with no name,
such as one constructed by @racket[test-begin].
}
@defparam[current-test-case-around handler (-> (-> any) any)]{
This parameter handles evaluation of test cases. The value
of the parameter is a function that is passed a thunk (a
function of no arguments). The function, when applied,
evaluates the expressions within a test case. The default
value of the @racket[current-test-case-around] parameters
evaluates the thunk in a context that catches exceptions and
prints an appropriate message indicating test case failure.
}
@defproc[(test-suite-test-case-around [thunk (-> any)]) any]{
The @racket[current-test-case-around] parameter is
parameterized to this value within the scope of a
@racket[test-suite]. This function creates a test case
structure instead of immediately evaluating the thunk.
}
@defproc[(test-suite-check-around [thunk (-> any/c)]) any/c]{
The @racket[current-check-around] parameter is parameterized
to this value within the scope of a @racket[test-suite].
This function creates a test case structure instead of
immediately evaluating a check.
}
@;{--------}
@section[#:tag "running"]{Programmatically Running Tests and Inspecting Results}
RackUnit provides an API for running tests, from which
custom UIs can be created.
@subsection{Result Types}
@defstruct[(exn:test exn) ()]{
The base structure for RackUnit exceptions. You should
never catch instances of this type, only the subtypes
documented below.}
@defstruct[(exn:test:check exn:test) ([stack (listof check-info)])]{
A @racket[exn:test:check] is raised when an check fails, and
contains the contents of the check-info stack at the
time of failure.}
@defstruct[test-result ([test-case-name (or/c string #f)])]{
A test-result is the result of running the test with
the given name (with @racket[#f] indicating no name is available).}
@defstruct[(test-failure test-result) ([result any])]{
Subtype of test-result representing a test failure.}
@defstruct[(test-error test-result) ([result exn])]{
Subtype of test-result representing a test error.}
@defstruct[(test-success test-result) ([result any])]{
Subtype of test-result representing a test success.}
@subsection{Functions to Run Tests}
@defproc[(run-test-case (name (or/c string #f)) (action (-> any)))
test-result]{
Runs the given test case, returning a result representing success,
failure, or error.
}
@defproc[(run-test (test (or/c test-case? test-suite?)))
(flat-murec-contract ([R (listof (or/c test-result? R))]) R)]{
Runs the given test (test case or test suite) returning a
tree (list of lists) of results}
Example:
@racketblock[
(run-test
(test-suite
"Dummy"
(test-case "Dummy" (check-equal? 1 2))))
]
@defproc[(fold-test-results [result-fn ('b 'c ... 'a . -> . 'a)]
[seed 'a]
[test (or/c test-case? test-suite?)]
[#:run run (string (() -> any) . -> . 'b 'c ...)]
[#:fdown fdown (string 'a . -> . 'a)]
[#:fup fup (string 'a . -> . 'a)])
'a]{
Fold @racket[result-fn] pre-order left-to-right depth-first
over the results of @racket[run]. By default @racket[run]
is @racket[run-test-case] and @racket[fdown] and
@racket[fup] just return the seed, so @racket[result-fn] is
folded over the test results.
This function is useful for writing custom folds (and hence UIs) over
test results without you having to take care of all the expected setup
and teardown. For example, @racket[fold-test-results] will run test
suite before and after actions for you. However it is still flexible
enough, via its keyword arguments, to do almost anything that
@racket[foldts] can. Hence it should be used in preference to @racket[foldts].
The @racket[result-fn] argument is a function from the results of
@racket[run] (defaults to a @racket[test-result]) and the seed to a
new seed.
The @racket[seed] argument is any value.
The @racket[test] argument is a test case or test suite.
The @racket[run] argument is a function from a test case name (string)
and action (thunk) to any values. The values produced by @racket[run]
are fed into the @scheme[result-fn].
The @racket[fdown] argument is a function from a test suite name
(string) and the seed, to a new seed.
The @racket[fup] argument is a function from a test suite name
(string) and the seed, to a new seed.
}
Examples:
The following code counts the number of successes:
@racketblock[
(define (count-successes test)
(fold-test-results
(lambda (result seed)
(if (test-success? result)
(add1 seed)
seed))
0
test))]
The following code returns the symbol @racket['burp] instead
of running test cases. Note how the @racket[result-fn] receives the
value of @racket[run].
@racketblock[
(define (burp test)
(fold-test-results
(lambda (result seed) (cons result seed))
null
test
#:run (lambda (name action) 'burp)))]
@defproc[(foldts [fdown (test-suite string thunk thunk 'a -> 'a)]
[fup (test-suite string thunk thunk 'a 'a -> 'a)]
[fhere(test-case string thunk 'a -> 'a)]
[seed 'a]
[test (or/c test-case? test-suite?)])
'a]{
The @racket[foldts] function is a nifty tree fold (created by Oleg
Kiselyov) that folds over a test in a useful way
(@racket[fold-test-results] isn't that useful as you can't specify
actions around test cases).
The @racket[fdown] argument is a function of test suite, test suite
name, before action, after action, and the seed. It is run when a
test suite is encountered on the way down the tree (pre-order).
The @racket[fup] argument is a function of test suite, test suite
name, before action, after action, the seed at the current level, and
the seed returned by the children. It is run on the way up the tree
(post-order).
The @racket[fhere] argument is a function of the test case, test case
name, the test case action, and the seed. (Note that this might change
in the near future to just the test case. This change would be to
allow @racket[fhere] to discriminate subtypes of test-case, which in
turn would allow test cases that are, for example, ignored).
}
Example:
Here's the implementation of @racket[fold-test-results] in terms of
@racket[foldts]:
@racketblock[
(define (fold-test-results suite-fn case-fn seed test)
(foldts
(lambda (suite name before after seed)
(before)
(suite-fn name seed))
(lambda (suite name before after seed kid-seed)
(after)
kid-seed)
(lambda (case name action seed)
(case-fn
(run-test-case name action)
seed))
seed
test))
]
If you're used to folds you'll probably be a bit surprised that the
functions you pass to @racket[foldts] receive both the structure they
operate on, and the contents of that structure. This is indeed
unusual. It is done to allow subtypes of test-case and test-suite to
be run in customised ways. For example, you might define subtypes of
test case that are ignored (not run), or have their execution time
recorded, and so on. To do so the functions that run the test cases
need to know what type the test case has, and hence is is necessary to
provide this information.
If you've made it this far you truly are a master RackUnit hacker. As
a bonus prize we'll just mention that the code in
@racketfont{hash-monad.rkt} and @racketfont{monad.rkt} might be of
interest for constructing user interfaces. The API is still in flux,
so isn't documented here. However, do look at the implementation of
@racket[run-tests] for examples of use.

View File

@ -8,7 +8,10 @@ bindings that a module does not provide. It is useful for
testing the private functions of modules.
@defform[(require/expose module (id ...))]{
Requires @racket[id] from @racket[module] into the current module. It doesn't matter if the source module provides the bindings or not; @racket[require/expose] can still get at them.
Requires @racket[id] from @racket[module] into the current module. It
doesn't matter if the source module provides the bindings or not;
@racket[require/expose] can still get at them.
Note that @racket[require/expose] can be a bit fragile,
especially when mixed with compiled code. Use at your own risk!

View File

@ -15,6 +15,7 @@ from novices to experts.
@include-section["quick-start.scrbl"]
@include-section["philosophy.scrbl"]
@include-section["api.scrbl"]
@include-section["internals.scrbl"]
@include-section["release-notes.scrbl"]
@include-section["acknowledgements.scrbl"]

View File

@ -1,195 +0,0 @@
#lang scribble/doc
@(require "base.rkt")
@title[#:tag "running"]{Programmatically Running Tests and Inspecting Results}
RackUnit provides an API for running tests, from which
custom UIs can be created.
@section{Result Types}
@defstruct[(exn:test exn) ()]{
The base structure for RackUnit exceptions. You should
never catch instances of this type, only the subtypes
documented below.}
@defstruct[(exn:test:check exn:test) ([stack (listof check-info)])]{
A @racket[exn:test:check] is raised when an check fails, and
contains the contents of the check-info stack at the
time of failure.}
@defstruct[test-result ([test-case-name (or/c string #f)])]{
A test-result is the result of running the test with
the given name (with @racket[#f] indicating no name is available).}
@defstruct[(test-failure test-result) ([result any])]{
Subtype of test-result representing a test failure.}
@defstruct[(test-error test-result) ([result exn])]{
Subtype of test-result representing a test error.}
@defstruct[(test-success test-result) ([result any])]{
Subtype of test-result representing a test success.}
@section{Functions to Run Tests}
@defproc[(run-test-case (name (or/c string #f)) (action (-> any)))
test-result]{
Runs the given test case, returning a result representing success, failure, or error.}
@defproc[(run-test (test (or/c test-case? test-suite?)))
(R = (listof (or/c test-result R)))]{
Runs the given test (test case or test suite) returning a
tree (list of lists) of results}
Example:
@racketblock[
(run-test
(test-suite
"Dummy"
(test-case "Dummy" (check-equal? 1 2))))
]
@defproc[(fold-test-results [result-fn ('b 'c ... 'a . -> . 'a)]
[seed 'a]
[test (or/c test-case? test-suite?)]
[#:run run (string (() -> any) . -> . 'b 'c ...)]
[#:fdown fdown (string 'a . -> . 'a)]
[#:fup fup (string 'a . -> . 'a)])
'a]{
Fold @racket[result-fn] pre-order left-to-right depth-first
over the results of @racket[run]. By default @racket[run]
is @racket[run-test-case] and @racket[fdown] and
@racket[fup] just return the seed, so @racket[result-fn] is
folded over the test results.
This function is useful for writing custom folds (and hence
UIs) over test results without you having to take care of
all the expected setup and teardown. For example,
@racket[fold-test-results] will run test suite before and
after actions for you. However it is still flexible enough,
via its keyword arguments, to do almost anything that foldts
can. Hence it should be used in preference to foldts.
@racket[result-fn] is a function from the results of
@racket[run] (defaults to a @racket[test-result]) and the
seed to a new seed
Seed is any value
Test is a test-case or test-suite
Run is a function from a test case name (string) and action
(thunk) to any values.
FDown is a function from a test suite name (string) and the
seed, to a new seed
FUp is a function from a test suite name (string) and the
seed, to a new seed.}
Examples:
The following code counts the number of successes
@racketblock[
(define (count-successes test)
(fold-test-results
(lambda (result seed)
(if (test-success? result)
(add1 seed)
seed))
0
test))]
The following code returns the symbol @racket['burp] instead
of running test cases. Note how the result-fn receives the
value of run.
@racketblock[
(define (burp test)
(fold-test-results
(lambda (result seed) (cons result seed))
null
test
#:run (lambda (name action) 'burp)))]
@defproc[(foldts [fdown (test-suite string thunk thunk 'a -> 'a)]
[fup (test-suite string thunk thunk 'a 'a -> 'a)]
[fhere(test-case string thunk 'a -> 'a)]
[seed 'a]
[test (or/c test-case? test-suite?)])
'a]{
Foldts is a nifty tree fold (created by Oleg Kiselyov) that
folds over a test in a useful way (fold-test-results isn't
that useful as you can't specify actions around test cases).
Fdown is a function of test suite, test suite name, before
action, after action, and the seed. It is run when a test
suite is encountered on the way down the tree (pre-order).
Fup is a function of test suite, test suite name, before
action, after action, the seed at the current level, and the
seed returned by the children. It is run on the way up the
tree (post-order).
Fhere is a function of the test case, test case name, the
test case action, and the seed. (Note that this might change
in the near future to just the test case. This change would
be to allow fhere to discriminate subtypes of test-case,
which in turn would allow test cases that are, for example,
ignored).}
Example:
Here's the implementation of fold-test-results in terms of
foldts:
@racketblock[
(define (fold-test-results suite-fn case-fn seed test)
(foldts
(lambda (suite name before after seed)
(before)
(suite-fn name seed))
(lambda (suite name before after seed kid-seed)
(after)
kid-seed)
(lambda (case name action seed)
(case-fn
(run-test-case name action)
seed))
seed
test))
]
If you're used to folds you'll probably be a bit surprised
that the functions you pass to foldts receive both the
structure they operate on, and the contents of that
structure. This is indeed unusual. It is done to allow
subtypes of test-case and test-suite to be run in customised
ways. For example, you might define subtypes of test case
that are ignored (not run), or have their execution time
recorded, and so on. To do so the functions that run the
test cases need to know what type the test case has, and
hence is is necessary to provide this information.
If you've made it this far you truly are a master RackUnit
hacker. As a bonus prize we'll just mention that the code
in hash-monad.rkt and monad.rkt might be of interest for
constructing user interfaces. The API is still in flux, so
isn't documented here. However, do look at the
implementation of @racket[run-tests] for examples of use.