Testing framework for q (.qu
)
Overview
qcumber (or quke) is a unit testing framework for q/kdb+ code. The framework offers assertion based testing, benchmarking capabilities, and property based testing.
Basic usage
\l qcumber.q_
.qu.runTestFile `:test.quke
.qu.runTestFolder `:tests/
Writing tests
A test file is created as nested groups of feature, should, bench, and property test blocks.
File organization
Test file names must end in .quke
. Test files are commonly located in a module whose
name matches the function's module name, followed by .test
. For example, a test file
for the function .ab.c
would have the name c.quke
and be found in
the .ab.test
module (.ab.test/c.quke
).
Quke syntax overview
Here is a simple test file, containing feature
, should
, and expect
blocks. Most
tokens allow for a description to be written to the right of the token to help identify
the block and document the test.
feature .ab.c
should exhibit some behaviour
expect some output
1b / the test case
Formatting errors
If any formatting errors are detected in a quke file, no tests will be run on that file and an error message will be displayed to the user. Clicking on the error message will open the malformed quke file at the line number where the error was detected. If multiple errors exist, only the first error detected will be displayed to the user.
Many blocks contain q code (q function blocks
). If a q function block
in a given
file cannot compile, qcumber will report an error for that block. Depending on the parent
block, an error that occurs when a q function block
executes may cause the feature
to
abort. All errors will be displayed to the user. Every line (other than the first line) in
q q function block
must have leading whitespace; otherwise the block cannot compile
and it will error. This is due to the semantics of q.
qcumber blocks
The qcumber blocks are described in detail below:
Feature
feature
blocks form the main test block of a quke file. Each feature
block may
contain one or more should
, bench
, property
, before
, after
, before each
,
or after each
blocks. The should
, bench
, and property
blocks contain test
cases, while the other four blocks are used to prepare the test environment conditions.
A quke file must contain at least one feature
block.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
Feature | NA | Required: At least one block Optional: One or more should blocks One or more bench blocks One or more property blocks One or more before blocks One or more after blocks One or more before each blocks One or more after each blocks |
Yes | NA |
Examples
feature may have a description
before
.ab.num1: 0
after
delete num1 from `.ab
before each
.ab.num1: 1
after each
.ab.num1: 0
should may have a description
expect may have a description
.ab.num1 = 1
expect
.ab.num1 = 1
property may have a description
.qch.check .qch.forall[.qch.g.int[]] { x=x }
Multiple feature
s are allowed in an quke file.
feature
should
expect
1b
feature
should
expect
1b
Should
should
blocks enclose groups of expect
blocks.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
should | feature | One or more expect blocks | yes | NA |
Examples
feature
should
expect
1b
Multiple should
blocks are allowed within a feature
.
feature .ab.c
should may have a description
expect one
1b
expect two
1 = 1
expect three
any 0010b
should
expect
1b
Expect
expect
blocks are the assertion based unit test blocks for qcumber.
They should return a boolean value, indicating whether or not the test
passed, or the result of .qu.compare.
The function .qu.compare
accepts an expected and actual value, and can be used
in expect
blocks to report these values in the test results.
It returns 1b if the inputs match, or a dictionary of the actual and expected results if they differ.
The arguments to .qu.compare
are stored in the test results table.
This can consume significant memory for large values,
and performing the comparison in an expect
block would be more performant.
.qu.compare
replaces the deprecated to match
block.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
expect | should | Required: q function block | Yes | No |
Optional: (Deprecated) One to match block |
Examples
feature
should
expect
1b
feature
should
expect may have a description
// This block spans multiple lines,
// and is parsed into a single function
v:0;
v+:1;
v+:1;
v=2;
feature
should
// The following blocks pass
expect
3 ~ count 1 2 3
expect
3
should
// The following test will fail, reporting the actual and expected values
expect
.qu.compare[1; 2]
To match
The to match
block is deprecated, as unlike .qu.compare
it cannot be evaluated in an editor.
Use an expect
with .qu.compare
instead.
to match
blocks specify the expected result from an expect
block.
The test will fail unless both blocks return the same value.
The results from the to match
and expect
blocks are stored in the test results table.
This can consume significant memory for large values,
and performing the comparison in an expect
block would be more performant.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
to match | expect | q function block | yes | no |
Examples
feature
should
expect
count 1 2 3
to match
3
Bench
The bench
block performs benchmarking tests where the runtime
of a q function in a behaviour
block is compared to the
runtime of a baseline
block or to a timelimit set in a timelimit
block. The bench
block may contain behaviour
, baseline
, timelimit
,
setup
, teardown
, tolerance
, and replicate
blocks.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
bench | feature | Required:One behaviour block One baseline block, one timelimit block, or one of each If there is a tolerance block, one baseline block is required Optional: One or more setup blocks One or more teardown blocks One tolerance block One replicate block |
yes | NA |
When running functions that are relatively close in runtime (for example,
til 10000
as a baseline
function and til 9000
as a behaviour
function),
the baseline
test will sometimes fail even though the behaviour
function
should run slightly faster. This occurs due to overhead from memory allocation,
and may cause the bench
test(s) to fail. There are two ways of dealing with
this issue.
1) The user can increase the number of replicates of the run, so that any variation
in runtime due to memory allocation will average out.
2) The user can set the number of secondary threads in the workspace to zero. Doing so
allows garbage collection to run after each replicate of the bench
test. Although
this makes every run of the baseline
or behaviour
q function block slightly
slower, it makes runtimes for each function more consistent. This strategy almost
always guarantees that the expected functions will run faster. Using both solutions
together tends to yield the best results.
The smallest measurable time difference can vary depending on the operating system.
The runtimes of baseline
and behaviour
block functions will be indistinguishable
if their runtime is less than the smallest measurable time difference. The smallest
measurable time difference is 1 microsecond for most modern operating systems, though
for some older Windows versions it is known to be closer to 1 millisecond.
Within a bench
test, up to four sub-tests may be run. If any of these tests fail
then the entire bench
test will be considered to have failed. These tests are
detailed in the following table. To find out more about these tests see the blocks
listed in the relevant blocks
column.
name | relevant blocks | description |
---|---|---|
Baseline | behaviour baseline | Indicates that the behaviour ran slower than the baseline . This test only runs if there is a baseline block and no tolerance block. |
Timelimit | behaviour timelimit | Indicates that the behaviour had a runtime greater than a certain time (in ms). This test only runs if there is a timelimit block. |
Upper Tolerance | behaviour behaviour tolerance | Indicates that the behaviour had a runtime greater than some percentage (the upper tolerance value) of the baseline runtime. |
Lower Tolerance | behaviour behaviour tolerance | Indicates that the behaviour had a runtime greater than some percentage (the lower tolerance value) of the baseline runtime.This test only runs if there is a tolerance block with two values. |
Examples
bench
baseline
til 100000000
behaviour
til 1
feature // bench blocks must be wrapped in feature blocks
// a bench test must have either one baseline block, one timelimit block, or one of each
bench
baseline
til 100000000
behaviour
til 1
bench // a feature may contain several bench blocks
timelimit
100
behaviour
til 1
bench may have a description
timelimit
100
baseline
til 10000000
behaviour
til 1
feature
bench
setup // multiple setup blocks are allowed
.ab.num1 : 1
setup
.ab.num2 : 1
baseline
til 100000000
behaviour
til 1
teardown // multiple teardown blocks are allowed
delete num1 from `.ab
teardown
delete num2 from `.ab
feature
bench
replicate
10 // a single replicate is allowed
tolerance
10 // a single tolerance is allowed
timelimit
10 // a single timelimit is allowed. A timelimit block is required if there is no baseline block.
baseline
til 100000000 // a single baseline is allowed. A baseline block is required if there is no timelimit or if there is a tolerance block.
behaviour // a single behaviour is required
til 1
Behaviour
behaviour
blocks contain the q function whose runtime is being benchmarked. In a
bench
block, the behaviour
block runtime is compared to either a timelimit,
the runtime of a baseline
block, or both. A bench
block must have exactly
one behaviour
block.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
behaviour | bench | q function block | yes | no |
Note: behaviour
may alternatively be spelled behavior
in the quke file.
Examples
behaviour
til 20 // q function block
feature
bench
timelimit
100 // timelimit of 100 milliseconds
baseline
til 10000000 // runtime approximately 10 milliseconds
behaviour may have a description // behaviour blocks must be wrapped in bench blocks
til 1 // this will have a runtime within the baseline runtime and within the timelimit, and the test will pass
Baseline
baseline
blocks contain the q function whose runtime is being compared to a
behaviour
block. A bench
block may have at most one baseline
block.
A bench
block must contain a baseline
block if no timelimit
block is present
or if there is a tolerance
block. If no tolerance
block is present, the
behaviour
block must have a runtime less than or equal to the runtime of
the baseline
block to pass the Baseline
test. If a tolerance
block is present
then the behaviour
block must have a runtime within some percentages (set by
the tolerance
block) of the baseline
block runtime to pass the
Upper Tolerance
and/or Lower Tolerance
tests.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
baseline | bench | q function block | yes | no |
Examples
baseline
til 200 // q function block
feature
bench
baseline may have a description // baseline blocks must be wrapped in bench blocks
til 10000000 // runtime approximately 10 milliseconds
behaviour
til 1 // this will have a runtime within the baseline runtime, and the test will pass
Timelimit
timelimit
blocks set the maximum runtime (in milliseconds) for a behaviour
block.
A bench
block may have at most one timelimit
block.
If no baseline
block is present a bench
block must have a timelimit
block.
The behaviour
block must have a runtime less than
or equal to the timelimit to pass the Timelimit
test (see above).
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
timelimit | bench | One long datatype | no | NA |
Examples
timelimit
10 // timelimit of 10 milliseconds
feature
bench
timelimit // timelimit blocks must be wrapped in bench blocks
100 // timelimit of 10 milliseconds
behaviour
til 1 // this will have a runtime less than the timelimit, and the test will pass
Tolerance
tolerance
blocks set tolerance limits for the benchmark tests. The tolerance
block
may contain one or two numerical values. If one value is present it is the upper
tolerance value. If two values are present the larger one is the upper tolerance
value and the smaller one is the lower tolerance value.
Two tolerance tests exist: the Upper Tolerance
test and the Lower Tolerance
test.
In the upper tolerance test, the behaviour
must execute with a runtime less than
or equal to some percentage (the upper tolerance value) of the baseline
runtime to
pass the Upper Tolerance
test.
In the lower tolerance test, the behaviour
must execute with a runtime greater than
or equal to some percentage (the lower tolerance value) of the baseline
runtime to
pass the Lower Tolerance
test. The lower tolerance test only runs if there is
a lower tolerance value.
A bench
block that contains a tolerance block must also have a baseline
block.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
tolerance | bench | One or two float or long datatypes | no | NA |
Examples
tolerance
90 // a tolerance with only an upper tolerance value of 90%
// the behaviour must have a runtime less than 90% of the baseline runtime to pass the upper tolerance test
// the lower tolerance test will not run
tolerance
10 90 // a tolerance block with both upper and lower tolerance values of 90% and 10% respectively
// the behaviour must have a runtime less than 90% of the baseline runtime to pass the upper tolerance test
// the behaviour must have a runtime greater than 10% of the baseline runtime to pass the lower tolerance test
tolerance
10.0 120.0 // a tolerance block with both the upper and lower tolerance values as floats
// the upper tolerance value can be greater than 100%
feature
bench
baseline // if a tolerance block is present there must be a baseline block
til 10000000
behaviour
til 5000000
tolerance // tolerance blocks must be wrapped in bench blocks
10 90 // the behaviour will have a runtime within the given percentages of the baseline runtime
Replicate
replicate
blocks set the number of replicates to be performed on the test functions
of the bench
block. The average runtimes will be used in the benchmarking tests.
The number of replicates defaults to 1 if no replicate block is present. The number
of replicates must be greater than zero.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
replicate | bench | One long datatype that is greater than zero | no | NA |
Examples
replicate
10 // 10 replicates
feature
bench
replicate // replicate blocks must be wrapped in bench blocks
10 // 10 replicates. The behaviour will be executed 10 times, and its average runtime will be found.
timelimit
100
behaviour
til 1
feature
bench
replicate
10 // 10 replicates. The behaviour and baseline will each execute 10 times, and their average runtimes will be found and compared.
baseline
10000000
behaviour
til 1
Setup
setup
blocks execute at the beginning of a bench
block. They are used for test
preparation by, for example, setting global variables to be used in the tests.
Errors thrown in setup
blocks cause the feature
to abort.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
setup | bench | q function block | no | yes |
Examples
setup
a::1 // q function block
feature
bench
setup // setup blocks must be wrapped in bench blocks
.ab.num1 : 1 // the setup runs at the beginning of the bench block
baseline
til 10000000
behaviour
til 1
feature
bench
setup
.ab.num1 : 1 // multiple setups are allowed
setup
.ab.num1 : 1
baseline
til 10000000
behaviour
til 1
Teardown
teardown
blocks execute at the beginning of a bench
block. They are used for
test cleanup. For example, deleting global variables from the workspace.
Errors thrown in teardown
blocks cause the feature
to abort.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
teardown | bench | q function block | no | yes |
Examples
teardown
a::0 // q function block
feature
bench
setup
.ab.num1 : 1
baseline
til 10000000
behaviour
til 1
teardown // setup blocks must be wrapped in bench blocks
delete num1 from `.ab // the teardown runs at the end of the bench block
feature
bench
setup
.ab.num1 : 1;
.ab.num2 : 2;
baseline
til 10000000
behaviour
til 1
teardown
delete num1 from `.ab // multiple teardowns are allowed
teardown
delete num2 from `.ab
Property
property
blocks allow for property based testing using the QuickCheck framework
provided by the .qch
module. This allows multiple tests to be run with randomly
generated input parameters. Although QuickCheck tests can be run within an expect
block, the property
block has the benefit of displaying any counter examples to
the user.
A property
block must contain q that returns the results of a valid QuickCheck
expression. The valid QuickCheck expression must contain the function .qch.check
,
followed by one of the seven .qch.forall
functions. See the .qch
module for
more information. This expression returns a dictionary indicating whether the
test passed or failed, any counter-examples to the test, and any errors that occurred
while running the test.
A handy .qch
function to use is .qch.with.times
. This allows the user to
set the number of samples to test in a given property block. An example
of .qch.with.times
, along with other property
blocks, can be seen below.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
property | feature | valid QuickCheck expression | yes | no |
Examples
property
// a valid QuickCheck expression, with the function .qch.check followed by a .qch.forall function
.qch.check
.qch.forall[.qch.g.int[]]{
x=x
}
feature
property another property // property blocks must be wrapped in feature blocks
// another valid QuickCheck expression, containing .qch.check followed by the .qch.forall2 function
.qch.check
.qch.forall2[.qch.g.int[];.qch.g.int[]]{
all .axq.isInt each x, y
}
feature
property
a::1; // additional q code is allowed prior to the QuickCheck expression in the property block
.qch.check
.qch.forall[.qch.g.int[]]{
1 in a,x
}
// By default, QuickCheck does 100 samples to look for errors
// The function `.qch.with.times` can be used to alter the number of samples.
feature
property
.qch.check
.qch.with.times[10] // Set the number of samples to 10
.qch.forall[.qch.g.int[]]{
1 = x
}
Before
before
blocks execute after any skip if
blocks in a feature
. They are used for
test preparation. For example, setting global variables to be used in the tests.
Errors thrown in before
blocks cause the feature
to abort.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
before | feature | q function block | no | yes |
Examples
before
.ab.num1 : 1 // q function bloc
feature
before // before blocks must be wrapped in feature blocks
.ab.num1 : 1 // before blocks execute at the start of their parent feature
feature
before
.ab.num1 : 2
feature // multiple before blocks are allowed in a feature
before
.ab.num1 : 1
before
.ab.num2 : 2
Before each
before each
blocks execute prior to each should
and each property
block in
a feature
block that is not skipped. They are used for test preparation.
For example, setting global variables to be used in the tests. Errors thrown in
before each
blocks cause the feature
to abort.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
before each | feature | q function block | no | yes |
Examples
before each
.ab.num1 : 1 // q function block
feature
before each // before each blocks must be wrapped in feature blocks
.ab.num1 : 1
before each // multiple before each blocks are allowed in a feature
.ab.num2 : 2
feature
before each
.ab.num1 : 1 // this before each block executes prior to both of the should blocks
should
expect
show .ab.num1; // will display 1
.ab.num1 :2;
1b
expect
show .ab.num1; // will display 2
.ab.num1 :2;
1b
should
expect
show .ab.num1; // will display 1
.ab.num1 : 2;
1b
After
after
blocks execute at the end of a feature
block. They are used for test
cleanup. For example, deleting any global variables defined by the test.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
after | feature | q function block | no | no |
Examples
after
.ab.num1 : 1 // q function block
feature
after // after blocks must be wrapped in feature blocks
.ab.num1: 1
after // multiple after blocks are allowed in a feature
.ab.num2 : 2
feature // after blocks execute at the end of their parent feature
after
.ab.num1: 1
feature
after
.ab.num2 : 2
After each
after each
blocks execute after each should
and each property
block in
a feature
block that is not skipped. They are used for test cleanup. For example,
deleting any global variables defined by the test. Errors thrown in after each
blocks cause
the feature
to abort.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
after each | feature | q function block | no | yes |
Examples
after each
.ab.num1 : 1 // q function block
feature
after each // after each blocks must be wrapped in feature blocks
.ab.num1 : 1
after each // multiple after each blocks are allowed in a feature
.ab.num2 : 2
feature
before
.ab.num1 : 1
after each
.ab.num1 : 2 // this after each block executes after every should block
should
expect
show .ab.num1; // will display 1
1b
expect
show .ab.num1; // will display 1
1b
should
expect
show .ab.num1; // will display 2
1b
Skip if
skip if
blocks execute at the beginning of a feature
. They are used to validate conditions and
determine whether or not the tests should be run. If the block returns boolean true (1b
), then the
feature
is skipped, and all of its blocks are reported as skipped blocks.
If the skip if
block returns boolean false (0b
), then feature
is executed.
If a non-boolean value is returned, or if an error is thrown, the feature
aborts.
name | parent block | content | description allowed | abort on error |
---|---|---|---|---|
skip if | feature | q function block | no | yes |
Examples
skip if
.z.o like "l*" // q function block, will skip if os is linux
feature // multiple "skip if" blocks are allowed in a feature
skip if
.z.o like "m*"
skip if
`w64 ~ .z.o
// "skip if" blocks execute at the start of their parent feature, and determine whether the rest of the feature is tested
feature
skip if
.z.o like "m*"
feature
skip if
`w64 ~ .z.o
Skipping tests
It is possible to skip individual test blocks by prepending an x
to the block name.
The test blocks that may be skipped are feature
, should
, expect
, bench
, and
property
. For example, to skip an expect
block, replace the expect
with
xexpect
. Skipping a test causes it to be completely ignored during the test run.
No parsing errors will be reported for these blocks. Skipping a block may cause a
parse error in any enclosing blocks if that block was expected in the
enclosing block. Any skipped tests will be reported in the output of the test runner.
It is also possible to skip tests dynamically using a skip if
block. See above for details.
Running qcumber
Two functions are provided to run test files programmatically: .qu.runTestFile and .qu.runTestFolder. The arguments to both functions are a string or symbol which represents the path to the file or folder respectively. Both relative and absolute paths are accepted. The function .qu.runTestFolder will recursively find and run all test files within a folder.
.qu.runTestFolder "folder"
.qu.runTestFile "folder/file"
.qu.runTestFolder `:folder
.qu.runTestFile `:folder/file
If generic null is passed to .qu.runTestFolder then it will recursively run all tests in the current directory.
.qu.runTestFolder[]
The results dictionary has the structure:
key | value |
---|---|
allTestResults | A table containing the results from all tests. |
allFailedTestResults | A table containing the results from all failed tests. |
allSkippedTestResults | A table containing the tests that were skipped. |
parseErrorList | A list of strings reporting every quke file with parse errors. |
The result table reported by the keys allTestResults
, allFailedTestResults
,
and allSkippedTestResults
have the following schema:
column | type | description |
---|---|---|
namespace | symbol | The folder path. |
fileName | symbol | The name of the file. |
feature | symbol | The name of the feature. |
block | symbol | The block type. |
description | string | A description of the block. |
expectations | string | If the block is `should , then this will be the description associated with individual expect block in the should block. |
line | long | The line number in the file. |
success | boolean | Whether or not the test was successful. |
result | any | The result returned by the test. Differs depending on the test block type. |
error | string | Any errors found. |
aborted | boolean | Whether or not the test was aborted. |
skipped | boolean | Whether or not the test was skipped. |
parseError | boolean | Whether or not the test file had a parse error. |
start | timestamp | The start time. |
time | timespan | The time taken to run the test. |
In the above table, the result column may have different structures depending on
the block type. If the block
is `should
, then the result column will contain
a dictionary with the following structure:
key | value type | value |
---|---|---|
expect | any | If the test failed, the result of the expect block, or .qu.compare . Null otherwise. |
toMatch | any | If the test failed, the result of .qu.compare , or 1b if .qu.compare was not used. Null otherwise. |
expectError | string | Any errors thrown by the expect block. |
toMatchError | string | Any errors thrown by a deprecated to match block. |
If the block
is `bench
, then the result column will contain a dictionary
with the following structure:
key | value type | value |
---|---|---|
baseline | string | The description of the baseline block. |
behaviour | string | The description of the behaviour block. |
baselineError | string | Any errors thrown by the baseline block. |
behaviourError | string | Any errors thrown by the behaviour block. |
passedBaseline | boolean | Whether or not the Baseline test passed. |
passedTimelimit | boolean | Whether or not the Timelimit test passed. |
passedLowerTolerance | boolean | Whether or not the Lower Tolerance test passed. |
passedUpperTolerance | boolean | Whether or not the Upper Tolerance test passed. |
timeBehaviour | float | The runtime of the behaviour block in ms. |
timeBaseline | float | The runtime of the behaviour block in ms. Null if there was no baseline block. |
timelimit | long | The set timelimit in ms. Null if there was no timelimit block. |
If the block
is `property
, then the result column will contain a dictionary
with the following structure:
key | value type | value |
---|---|---|
output | string | The raw output from the property block. This should normally correspond to a QuickCheck dictionary (see .qch for more details). If the property block was improperly formatted then it may have a different data structure. |
formatError | boolean | Whether or not the property block was properly formatted. If this is true (1b) then the block did not correspond to a valid QuickCheck expression, and you cannot query into the output column to investigate the expected QuickCheck dictionary. |
failed | any | The counter example. If possible this will be the shrunk value from .qch . |
If the block
is none of the above, the then the result column holds generic null.
To load qcumber into a q process, refer to the Running Libraries section of
the Notes & TroubleShooting
guide.
Mocking functions
Qcumber provides utilities to mock user functions. This can facilitate testing by allowing users to replace the implementation of functions with their mocked functions that allow verifying that the correct arguments are supplied, or by controlling the return type to easily test the calling functions.
To mock functions, either use .qu.stub.single
to mock a single function or .qu.stub.mock
to mock a set of
functions. .qu.stub.single
takes the name of the function to mock and the replacement function as arguments.
The argument to .qu.stub.mock
is a list, where each item is a list consisting of the name of the function
to replace and the replacement function.
To restore all of the mocked functions to their original versions call .qu.stub.restoreAll
. To restore only a
subset of functions, call .qu.stub.restore
. The argument to .qu.stub.restore
is a list of the functions
to restore. If a given function was mocked multiple times then .qu.stub.restore
will only restore the function
to the version prior to its current version.
Debugging tests
To debug a failing test, errors within tests can be propagated rather than trapped by calling
.qu.setBreakOnErrors 1b
Calling the same function with 0b
resets this behaviour.
Examples
File examples
Examples test files are presented below. These show the main types of qcumber blocks:
- A feature block, illustrating the terminology used in the documentations.
feature // qcumber token: designates a feature block
should // qcumber token: designates a should block
expect // qcumber token: designates a expect block
v:0; // |
v+:1; // |
v+:1; // | q function block: this block spans multiple lines but is parsed into a single function
v+:1; // |
v = 3; // |
expect // qcumber token: designates a second expect block
1b // q function block: a second q function, though this one spans only one line
expect // qcumber token: designates a second expect block
[a:8; // |
b:8; // | q function block: a q function block may optionally be enclosed by a single pair of square brackets
a=b] // |
- Expect block tests.
// expect blocks are used to run assertion tests
feature
should this is a should
expect this is an expect
1b
expect this is an expect
1 = 1
- A bench block test.
// bench blocks are used to perform runtime tests
feature
bench this is a bench
setup
.ab.num1 : 1
timelimit
100
tolerance
10 100
replicate
1000
baseline this is a baseline
til 10000
behaviour this is a behaviour
til 5000
teardown
delete num1 from `.ab
- A property block test.
// property blocks are used to perform tests on sets of randomly generated values
feature
property this is a property
.qch.check
.qch.forall[.qch.g.int[]]{
x=x
}
- A quke file showing all types of blocks, as well as multiple features. In additions, comments are shown after tokens that allow descriptions.
// A feature block containing all of the possible qcumber blocks
feature this is a feature // descriptions are allowed after feature blocks
before
.ab.num1 : 0
after
delete num1 from `.ab
before each
.ab.num1 : 1
after each
.ab.num1 : 0
should this is a should // descriptions are allowed after should blocks
expect this is an expect // descriptions are allowed after expect blocks
.ab.num1 = 1
expect this is another expect // descriptions are allowed after expect blocks
.ab.num1 = 1
bench this is a bench // descriptions are allowed after bench blocks
setup
.ab.num2 : 1
timelimit
100
tolerance
10 100
replicate
1000
baseline this is a baseline // descriptions are allowed after baseline blocks
til 10000
behaviour this is a behaviour // descriptions are allowed after behaviour blocks
til 5000
teardown
delete num2 from `.ab
property this is a property // descriptions are allowed after property
.qch.check
.qch.forall[.qch.g.int[]]{
x=x
}
Failing test examples
Examples of the expected outputs for failing tests are shown below:
- An example failing expect test, along with the failed test output, is shown below.
Consider a test file .ab.test/expectExample.quke with the following content:
feature
should
expect
0b
This test will fail because the expect
block returns a value other than boolean
true (in this case, it returns boolean false). When the user runs the test, it will
display the following failed test message:
1 of 1 test failed
Failed Tests : 1
feature
should
expect (.ab.test/expectExample.quke.quke:3)
Expected Result: 1b
Actual Result: 0b
Clicking on the tag (.ab.test/expectExample.quke:3) will open the failed test file on the line with the block that failed (in this case, line 3). An example of a failing bench test, along with the failed test output, is shown below.
Consider a test file .ab.test/benchExample.quke with the following content:
feature
bench
baseline
til 1
behaviour
til 10000000
When the user runs the test, it will display a failed test message similar to the following:
1 of 1 test failed
Failed Tests : 1
feature
bench (.ab.test/benchExample.quke:2)
Behaviour Runtime: 8.85 ms
Baseline Runtime: 0.001 ms
Baseline test: Failed
In this case, the Baseline test
failed, which indicates that the behaviour
ran slower than the baseline
. The runtimes for the behaviour
and baseline
as well as the timelimit (if present) are reported to the user.
An example of a failing property test, along with the failed test output, is shown below.
Consider a test file .ab.test/propertyExample.quke with the following content:
feature
property
.qch.check
.qch.forall[.qch.g.int[]]{
.axq.isFloat x
}
This test will fail because no integer is a float.
When the user runs the test, it will display a failed test message similar to the following:
1 of 1 test failed
Failed Tests : 1
feature
property (.ab.test/benchExample.quke:2)
Counter Example: ,0i
In this case, the counter example that broke the test is displayed to the user. If an error occurred in the QuickCheck expression, it will also be displayed to the user.
Skipped test examples
An example of a skipped expect test, along with the test output is shown below. Consider a test file .ab.test/skippedExample.quke with the following content:
feature skippedExample.quke
should exhibit a behaviour
xexpect a specific result
0b
expect a specific result
0b
This test has two failing expects, but only the latter is reported since the first test was skipped. When the user runs the test, it will display the following failed test message:
1 of 2 tests failed
1 test skipped
Failed Tests : 1
feature skippedExample
should exhibit a behaviour
expect a specific result (.ab.test/skippedExample.quke:6)
Expected Result: 1b
Actual Result: 0b
Skipped Tests : 1
Clicking on the tag (.ab.test/skippedExample.quke:6) will open the failed test file on line 6 where the block failed. Both the "Failed Tests" and "Skipped Tests" tags can be clicked to open an close drop down menus that describe the failed and skipped tests in further detail. To view the skipped tests, click on the arrow next to the "Skipped Tests" (not visible in this document).
Block summary
name | description | parent block | content | description allowed | abort on error |
---|---|---|---|---|---|
feature | Contains all other blocks | NA | Required: At least one child block Optional: One or more before blocks One or more after blocks One or more before each blocks One or more after each blocks One or more should blocks One or more bench blocks One or more property blocks |
yes | NA |
should | Wrapper block to contain expects | feature | One or more expect blocks | yes | NA |
expect | Assertion test block | should | Required: q function block Optional: (Deprecated) one to match block |
yes | no |
to match | (Deprecated) Expected result from preceding expect block | expect | q function block | yes | no |
bench | Benchmarking test block | feature | Required: One behaviour block One baseline block, one timelimit block, or one of each If there is a tolerance block, one baseline block is required Optional: One or more setup blocks One or more teardown blocks One tolerance block One replicate block |
yes | NA |
behaviour | Code whose runtime is compared to a timelimit or the baseline runtime | bench | q function block | yes | no |
baseline | Baseline code whose runtime is compared to behaviour function | bench | q function block | yes | no |
timelimit | Timelimit for behaviour code | bench | One long datatype | no | NA |
tolerance | Upper and lower tolerance percentages for baseline/behaviour runtime comparison | bench | One or two float or long datatypes | no | NA |
replicate | Number of times to measure runtime of behaviour and baseline code | bench | One long datatype that is greater than zero | no | NA |
setup | Runs code at start of a bench | bench | q function block | no | yes |
teardown | Runs code at end of a bench | bench | q function block | no | yes |
property | Property test block | feature | valid QuickCheck expression | yes | no |
before | Runs code at the start of a feature | feature | q function block | no | yes |
before each | Runs code prior to each should in a feature | feature | q function block | no | yes |
after | Runs code at the end of a feature | feature | q function block | no | no |
after each | Runs code after each should in a feature | feature | q function block | no | yes |
skip if | Runs code at the start of a feature to decide if the rest of the feature is run | feature | q function block | no | yes |
.qu.compare
Compares two values such that the actual vs. expected values can be included in qcumber results
Parameters:
Name | Type | Description |
---|---|---|
actual | any | The output being tested |
expected | any | The reference value |
Returns:
Type | Description |
---|---|
boolean | dict (expect: any; toMatch: any) | 1b if the inputs match, or a dictionary of the actual and expected results if they differ |
.qu.runTestFile
Runs a single test file with default settings
Parameter:
Name | Type | Description |
---|---|---|
path | string | symbol | The folder path |
Returns:
Type | Description |
---|---|
.qu.results | See .qu/README.md for more information about the structure of the results table given different block types. |
Example:
.qu.runTestFile "file.quke"
.qu.runTestFileWithSettings
Runs a single test file
Parameters:
Name | Type | Description |
---|---|---|
path | string | symbol | The folder path |
sts | .qu.ty.userSettings | null | The qcumber settings |
Returns:
Type | Description |
---|---|
.qu.results | See Running qcumber for more information about the structure of the results table given different block types. |
Example:
.qu.runTestFile "file.quke"
.qu.runTestFolder
Runs all tests in a folder with default settings
Parameter:
Name | Type | Description |
---|---|---|
path | string | symbol | null | The folder path. Null is replaced with "." namespace |
Returns:
Type | Description |
---|---|
.qu.results | See Running qcumber for more information about the structure of the results table given different block types. |
Example:
.qu.runTestFolder "folder"
.qu.runTestFolderWithSettings
Runs all tests in a folder
Parameters:
Name | Type | Description |
---|---|---|
path | string | symbol | null | The folder path. Null is replaced with "." namespace |
sts | .qu.ty.userSettings | null | The qcumber settings |
Returns:
Type | Description |
---|---|
.qu.results | See Running qcumber for more information about the structure of the results table given different block types. |
Example:
.qu.runTestFolder "folder"
.qu.setBreakOnErrors
Allows errors from unit tests to propagate, facilitate debugging
Parameter:
Name | Type | Description |
---|---|---|
x | boolean | 1b to not trap errors in unit tests |
Returns:
Type | Description |
---|---|
null |
.qu.settings.default
the default settings for qcumber
.qu.ty.onLoad
The type definitions for qcumber
Returns:
Type | Description |
---|---|
Type |