Testworks Reference#
See also: Testworks Usage
The testworks library exports a single module named testworks.
Suites, Tests, and Benchmarks#
- test-definer Macro#
Define a new test.
- Signature:
define test test-name (#key expected-to-fail-reason, expected-to-fail-test, tags) body end
- Parameters:
test-name – Name of the test; a Dylan variable name.
expected-to-fail-reason (#key) – A
<string>
or#f
. The reason this test is expected to fail.expected-to-fail-test (#key) – An instance of
<function>
. A function to decide whether the test is expected to fail.tags (#key) – A list of strings to tag this test.
Tests may contain arbitrary code, and must have at least one assertion. That is they must call one of the
assert-*
orcheck-*
macros. If they have no assertions they are given aNOT IMPLEMENTED
result. If any assertion fails the test fails, but any remaining assertions in the test are still executed. If code outside of an assertion signals an error, the test is marked asCRASHED
and the rest of the test is skipped.expected-to-fail-test, if supplied, is a function of no arguments that returns a true value if the test is expected to fail. Such a failure is then classified as an
EXPECTED FAILURE
result (which is a passing result). If the test passes rather than failing, it has anUNEXPECTED SUCCESS
result (which is a type of failure). A common usage is to make expected failure conditional on the OS:method () $os-name == #"win32" end
. This option has no effect on tests which areNOT IMPLEMENTED
orCRASHED
.expected-to-fail-reason should be supplied if the test is expected to fail. It is good practice to reference a bug (a URL or at least a bug number).
Note
If expected-to-fail-reason is supplied without expected-to-fail-test the test is implicitly always expected to fail.
tags provide a way to select or filter out specific tests during a test run. The Testworks command-line (provided by
run-test-application
) has a--tag
option to only run tests that match (or don’t match) specific tags.
- benchmark-definer Macro#
Define a new benchmark.
- Signature:
define benchmark benchmark-name (#key expected-to-fail-reason, expected-to-fail-test, tags) body end
- Parameters:
benchmark-name – Name of the benchmark; a Dylan variable name.
expected-to-fail-reason (#key) – A
<string>
or#f
. The reason this benchmark is expected to fail.expected-to-fail-test (#key) – An instance of
<function>
. A function to decide whether the benchmark is expected to fail.tags (#key) – A list of strings to tag this benchmark.
Benchmarks may contain arbitrary code and do not require any assertions. If the benchmark signals an error it is marked as
CRASHED
. Other than this, and minor differences in how the results are displayed, benchmarks are the same as tests.
- benchmark-repeat Macro#
Repeatedly execute a block of code, recording profiling information for each execution.
- Signature:
benchmark-repeat (#key iterations = 1) body end
- Parameters:
iterations – Number of times to execute body.
Results for benchmarks that call benchmark-repeat display the min, max, mean, and median run times across all iterations.
It may be necessary to use
--report=full
to display detailed benchmark statistics.At the beginning of each iteration benchmark-repeat first collects garbage to attempt to reduce variability across different executions.
- suite-definer Macro#
Define a new test suite.
- Signature:
define suite suite-name (#key setup-function cleanup-function) body end
- Parameters:
suite-name – Name of the suite; a Dylan variable name.
setup-function (#key) – A function to perform setup before the suite starts.
cleanup-function (#key) – A function to perform teardown after the suite finishes.
Suites provide a way to group tests and other suites into a single executable unit. Suites may be nested arbitrarily.
setup-function is executed before any tests or sub-suites are run. If setup-function signals an error the entire suite is skipped and marked as “crashed”.
cleanup-function is executed after all sub-suites and tests have completed, regardless of whether an error is signaled.
- interface-specification-suite-definer Macro#
Define a test suite to verify an API.
- Signature:
define interface-specification-suite suite-name () specs end;
- Parameters:
suite-name – Name of the suite; a Dylan variable name.
This macro is useful to verify that public interfaces to your library don’t change unintentionally.
specs are clauses separated by semicolons, specifying the attributes of an exported name. Each spec looks much like the definition of the name being tested. The following example has one of each kind of spec:
define interface-specification-suite time-specification-suite () sealed instantiable abstract class <time> (<object>); generic function parse-time (<string>, #"key") => (<time>); variable *foo* :: <string>; constant $unix-epoch :: <time>; end;
The following sections explain the syntax of each kind of spec in detail. Note that there is no way to verify macros automatically and therefore there is no “macro” spec.
class specs
Syntax: modifiers class name (superclasses) [, test-options ];
modifiers
sealed
oropen
,primary
orfree
,abstract
orconcrete
, andinstantiable
. Currently the first two pairs are unused, but you may want to specify them anyway, to keep the spec in sync with the code.If
instantiable
is specified, Testworks will try to make an instance of name by callingmake
with no arguments. If your class requires init arguments, you must define a method onmake-test-instance
:define method make-test-instance (class == <my-class>) => (instance :: <my-class>) make(<my-class>, ...init args...) end
name
Name of the class to verify.
superclasses
Comma-separated list of superclass names.
test-options
Any options valid for
test-definer
. For example,expected-to-fail-reason: "foo"
.function specs
Syntax: modifiers function name (parameter-types) => (value-types) [, test-options ];
modifiers
generic
name
Name of the function. Note that function specs should be used for functions created with
define function
(which are really just bare methods bound to a name as withdefine constant m = method() ... end
) and for generic functions.parameter-types
Comma-separated list of parameter type names, possibly empty. Where
#rest
,#key
, and#all-keys
appear in the corresponding function definition, use#"rest"
,#"key"
, and#"all-keys"
instead (i.e., with double quotes). Keyword arguments are specified without type qualifiers. Examples from the dylan-test-suite:open generic function make (<type>, #"rest", #"key", #"all-keys") => (<object>); open generic function copy-sequence (<sequence>, #"key", #"start", #"end") => (<sequence>);
value-types
Comma-separated list of return value type names, possibly empty.
test-options
Any options valid for
test-definer
. For example,expected-to-fail-reason: "foo"
.variable specs
Syntax: variable name :: type [, test-options ];
name
Name of the variable.
type
Type of the variable.
test-options
Any options valid for
test-definer
. For example,expected-to-fail-reason: "foo"
.constant specs
Syntax: constant name :: type [, test-options ];
name
Name of the constant.
type
Type of the constant.
test-options
Any options valid for
test-definer
. For example,expected-to-fail-reason: "foo"
.
Assertions#
Assertions are the smallest unit of verification in Testworks. They must appear within the body of a test.
Assertion macros that accept an argument that is the expected value as well as the expression that is to be tested typically expect the value first and the expression second. The macros don’t always require that this be the case:
assert-not-equal(5, 2 + 2);
assert-instance?(<integer>, 2 + 2);
All assertion macros accept a description of what is being tested as an optional final argument. The description should be stated in the positive sense. For example:
assert-equal(2, 2 + 2, "2 + 2 equals 2")
These are the available assertion macros:
- assert-true Macro#
Assert that an expression evaluates to a true value. Importantly, this does not mean the expression is exactly
#t
, but rather that it is not#f
. If you want to explicitly test for equality to#t
useassert-equal(#t, ...)
.- Signature:
assert-true expression [ description ]
- Parameters:
expression – any expression
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “two is less than three”. If no description is supplied one is automatically generated based on the text of the expression.
- Example:
assert-true(has-fleas?(my-dog)) assert-true(has-fleas?(my-dog), "my dog has fleas")
- assert-false Macro#
Assert that an expression evaluates to
#f
.- Signature:
assert-false expression [ description ]
- Parameters:
expression – any expression
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “two is less than three”. If no description is supplied one is automatically generated based on the text of the expression.
- Example:
assert-false(3 < 2) assert-false(6 = 7, "six equals seven")
- assert-equal Macro#
Assert that two values are equal using
=
as the comparison function. Using this macro is preferable to usingassert-true(a = b)
because the failure messages are much better when comparing certain types of objects, such as collections.- Signature:
assert-equal expression1 expression2 [ description ]
- Parameters:
expression1 – any expression
expression2 – any expression
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “two is less than three”. If no description is supplied one is automatically generated based on the text of the expression.
- Example:
assert-equal(2, my-complicated-method()) assert-equal(this, that, "this and that are the same")
- assert-not-equal Macro#
Assert that two values are not equal using
~=
as the comparison function. Using this macro is preferable to usingassert-true(a ~= b)
orassert-false(a = b)
because the generated failure messages can be better.- Signature:
assert-not-equal expression1 expression2 [ description ]
- Parameters:
expression1 – any expression
expression2 – any expression
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “two is less than three”. If no description is supplied one is automatically generated based on the text of the expression.
- Example:
assert-not-equal(2, my-complicated-method()) assert-not-equal(this, that, "this does not equal that")
- assert-signals Macro#
Assert that an expression signals a given condition class.
- Signature:
assert-signals condition, expression [ description ]
- Parameters:
condition – an expression that yields a condition class
expression – any expression
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “f() signals <error>”. If no description is supplied one is automatically generated based on the text of the expression.
The assertion succeeds if the expected condition is signaled by the evaluation of expression.
- Example:
assert-signals(<division-by-zero-error>, 3 / 0) assert-signals(<division-by-zero-error>, 3 / 0, "my super special description")
- assert-no-errors Macro#
Assert that an expression does not signal any errors.
- Signature:
assert-no-errors expression [ description ]
- Parameters:
expression – any expression
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “f(3) does not signal <error>”. If no description is supplied one is automatically generated based on the text of the expression.
The assertion succeeds if no error is signaled by the evaluation of expression.
Use of this macro is preferable to simply executing expression as part of the test body for two reasons. First, it can clarify the purpose of the test, by telling the reader “here’s an expression that is explicitly being tested, and not just part of the test setup.” Second, if the assertion signals an error the test will record that fact and continue, as opposed to taking a non-local exit. Third, it will show up in generated reports.
- Example:
assert-no-errors(my-hairy-logic()) assert-no-errors(my-hairy-logic(), "hairy logic completes without error")
- assert-instance? Macro#
Assert that the result of an expression is an instance of a given type.
- Signature:
assert-instance? type expression [ description ]
- Parameters:
type – The expected type.
expression – An expression.
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “f() returns an instance of <foo>”. If no description is supplied one is automatically generated based on the text of the expression.
- Discussion:
Warning
The arguments to this assertion follow the typical argument ordering of Testworks assertions with the desired value before the expression that represents the test. As such, the desired type is the first parameter to this assertion while it is the second parameter for
instance?
.- Example:
assert-instance?(<type>, subclass(<string>)); assert-instance?(<type>, subclass(<string>), "subclass returns type");
- assert-not-instance? Macro#
Assert that the result of an expression is not an instance of a given class.
- Signature:
assert-not-instance? type expression [ description ]
- Parameters:
type – The type.
expression – An expression.
description – An optional description of what the assertion tests. This may be a single value of any type or a format string and format arguments. It should be stated in positive form, such as “f() does not return a <string>”. If no description is supplied one is automatically generated based on the text of the expression.
- Discussion:
Warning
The arguments to this assertion follow the typical argument ordering of Testworks assertions with the desired value before the expression that represents the test. As such, the desired type is the first parameter to this assertion while it is the second parameter for
instance?
.- Example:
assert-not-instance?(limited(<integer>, min: 0), -1); assert-not-instance?(limited(<integer>, min: 0), -1, "values below lower bound are not instances");
Checks#
Checks are like Assertions but they do not cause the test to terminate when they fail or crash. Only use them if later checks or assertions do not depend on them passing and they won’t cause too many cascading failures (for example if they’re used in a tight loop).
Checks also differ from the assert-*
macros in that they require a
description (or “name”) as their first argument. We intend to fix this
inconsistency in the future.
These are the available checks:
- check Macro#
Perform a check within a test.
- Signature:
check name function #rest arguments
- Parameters:
name – An instance of
<string>
.function – The function to check.
arguments (#rest) – The arguments for
function
.
- Example:
check("Test less than operator", \<, 2, 3)
- check-condition Macro#
Check that a given condition is signalled.
- Signature:
check-condition name expected expression
- Parameters:
name – An instance of
<string>
.expected – The expected condition class.
expression – An expression.
- Example:
check-condition("format-to-string crashes when missing an argument", <error>, format-to-string("Hello %s"));
- check-equal Macro#
Check that 2 expressions are equal.
- Signature:
check-equal name expected expression
- Parameters:
name – An instance of
<string>
.expected – The expected value of
expression
.expression – An expression.
- Example:
check-equal("condition-to-string of an error produces correct string", "Hello", condition-to-string(make(<simple-error>, format-string: "Hello")));
- check-false Macro#
Check that an expression has a result of
#f
.- Signature:
check-false name expression
- Parameters:
name – An instance of
<string>
.expression – An expression.
- Example:
check-false("unsupplied?(#f) == #f", unsupplied?(#f));
- check-instance? Macro#
Check that the result of an expression is an instance of a given type.
- Signature:
check-instance? name type expression
- Parameters:
name – An instance of
<string>
.type – The expected type.
expression – An expression.
- Example:
check-instance?("subclass returns type", <type>, subclass(<string>));
- check-true Macro#
Check that the result of an expression is not
#f
.- Signature:
check-true name expression
- Parameters:
name – An instance of
<string>
.expression – An expression.
- Discussion:
Note that if you want to explicitly check if an expression evaluates to
#t
, you should usecheck-equal
.- Example:
check-true("unsupplied?($unsupplied)", unsupplied?($unsupplied));
Test Execution#
- run-test-application Function#
Run a test suite or test as part of a stand-alone test executable.
- Signature:
run-test-application #rest suite-or-test => ()
- Parameters:
suite-or-test – (optional) An instance of
<suite>
or<runnable>
. If not supplied then all tests and benchmarks are run.
This is the main entry point to run a set of tests in Testworks. It parses the command-line and based on the specified options selects the set of suites or tests to run, runs them, and generates a final report of the results.
Internally,
run-test-application
creates a<test-runner>
based on the command-line options and then callsrun-tests
with the runner and suite-or-test.
- test-option Function#
Return an option value passed on the test-application command line.
- Signature:
test-option name #key default => value
- Parameters:
- Values:
value – An instance of type
<string>
.
Returns an option value passed to the test on the test application command line, in the form
*name*=*value*
. If no option value was given, the default value is returned if one was supplied, otherwise an error is signalled.This feature allows information about external resources, such as path names of reference data files, or the hostname of a test database server, to be supplied on the command line of the test application and retrieved by the test.
- test-temp-directory Function#
Retrieve a unique temporary directory for the current test to use.
- Signature:
test-temp-directory => directory
- Values:
directory – An instance of type
<directory-locator>
.
Returns a directory (a
<directory-locator>
) that may be used for temporary files created by the test or benchmark. The directory is created the first time this function is called for each test or benchmark and is not deleted after the test run is complete in case it’s useful for post-mortem analysis. The directory is named_test/<user>-<timestamp>/<test-name>
and is rooted at$DYLAN
, if defined, or in the current directory otherwise.Note
In the
<test-name>
component of the directory both slash (/
) and backslash (\
) are replaced by underscore (_
).
- write-test-file Function#
Writes a file in the current test’s temp directory.
- Signature:
write-test-file filename #key contents => locator
- Parameters:
filename – An instance of
<pathname>
(i.e., a string or a locator). The name may be a relative path and if it contains the path separator character, subdirectories will be created.contents (#key) – An instance of
<string>
to be written to the file. Defaults to the empty string.
- Values:
locator – An instance of
<file-locator>
which is the full, absolute pathname of the created file.
When your test requires files to be present this is a handy utility to create them. Examples:
write-test-file("x.txt"); let locator = write-test-file("a/b/c.log", contents: "abc");