Writing and running tests in Raku
Testing code is an integral part of software development. Tests provide automated, repeatable verifications of code behavior, and ensures your code works as expected.
In Raku, the Test module provides a testing framework, used also by Raku's official spectest suite.
The testing functions emit output conforming to the Test Anything Protocol. In general, they are used in sink context:
ok check-name(, :), "name has a hyphen rather than '::'"
but all functions also return as a Boolean if the test has been successful or not, which can be used to print a message if the test fails:
ok check-name(, :), "name has a hyphen rather than '::'" \or diag "\nTo use hyphen in name, pass :relaxed-name to check-name\n";
Although it is possible to organize your tests differently, the typical Raku convention is for tests to live under the t
directory in the project's base directory.
A typical test file looks something like this:
use Test; # a Standard module included with Rakudouse lib 'lib';plan ;# .... testsdone-testing; # optional with 'plan'
We load the builtin Test
module and specify where our other libraries are. We then specify how many tests we plan to run (such that the testing framework can tell us if more or fewer tests were run than we expected) and when finished with the tests, we use done-testing to tell the framework we are done.
Note that routines in Test
module are not thread-safe. This means you should not attempt to use the testing routines in multiple threads simultaneously, as the TAP output might come out of order and confuse the program interpreting it.
There are no current plans to make it thread safe. If threaded-testing is crucial to you, you may find some suitable ecosystem modules to use instead of Test
for your testing needs.
Tests can be run individually by specifying the test filename on the command line:
$ raku t/test-filename.t
To run all tests in the directory recursively, prove6 application can be used.
You have to install it before using with zef:
$ zef install App::Prove6
You can run prove6
in a distribution directory this way:
$ prove6 --lib t/
The t/
argument specified directory that contains tests and the --lib
option is passed to include lib
directory into Raku distribution path, it is an equivalent of -Ilib
argument of raku
command.
For more documentation regarding prove6
usage refer to prove6.
To abort the test suite upon first failure, set the RAKU_TEST_DIE_ON_FAIL
environmental variable:
$ RAKU_TEST_DIE_ON_FAIL=1 raku t/test-filename.t
The same variable can be used within the test file. Set it before loading the Test
module:
BEGIN <RAKU_TEST_DIE_ON_FAIL> = 1;use Test;...
Note: Before Rakudo version 2020.05 the environment variable PERL6_TEST_DIE_ON_FAIL
was used to enable this feature, it is still supported but deprecated.
Test timing in microseconds can be emitted by setting the RAKU_TEST_TIMES
environmental variable:
$ env RAKU_TEST_TIMES=1 raku -e 'use Test; plan 1; pass sleep(1);'1..1# between two timestamps 0 microsecondsok 1 -# t=1000721
The same variable can be used within the test file. Set it before loading the Test
module:
BEGIN <RAKU_TEST_TIMES> = 1;use Test;...
Note: Before Rakudo version 2020.05 the environment variable PERL6_TEST_TIMES
was used to enable this feature, it is still supported but deprecated.
Tests plans use plan
for declaring how many plans are going to be done or, as might be the case, skipped. If no plan is declared, done-testing
is used to declare the end of the tests.
The Test
module exports various functions that check the return value of a given expression and produce standardized test output.
In practice, the expression will often be a call to a function or method that you want to unit-test. ok
and nok
will match True
and False
. However, where possible it's better to use one of the specialized comparison test functions below, because they can print more helpful diagnostics output in case the comparison fails.
is
and isnt
test for equality using the proper operator, depending on the object (or class) it's handled.
is-approx
compares numbers with a certain precision, which can be absolute or relative. It can be useful for numeric values whose precision will depend on the internal representation.
Structures can be also compared using is-deeply
, which will check that internal structures of the objects compared is the same.
You can use any kind of comparison with cmp-ok
, which takes as an argument the function or operator that you want to be used for comparing.
isa-ok
tests whether an object is of a certain type.
can-ok
is used on objects to check whether they have that particular method.
does-ok
checks whether the given variable can do a certain Role.
like
and unlike
check using regular expressions; in the first case passes if a match exists, in the second case when it does not.
Modules are tentatively loaded with use-ok
, which fails if they fail to load.
dies-ok
and lives-ok
are opposites ways of testing code; the first checks that it throws an exception, the second that it does not; throws-like
checks that the code throws the specific exception it gets handed as an argument; fails-like
, similarly, checks if the code returns a specific type of Failure. eval-dies-ok
and eval-lives-ok
work similarly on strings that are evaluated prior to testing.
The result of a group of subtests is only ok
if all subtests are ok
; they are grouped using subtest
.
Sometimes tests just aren't ready to be run, for instance a feature might not yet be implemented, in which case tests can be marked as todo
. Or it could be the case that a given feature only works on a particular platform - in which case one would skip
the test on other platforms; skip-rest
will skip the remaining tests instead of a particular number given as argument; bail-out
will simply exit the tests with a message.
If the convenience functionality documented above does not suit your needs, you can use the following functions to manually direct the test harness output; pass
will say a test has passed, and diag
will print a (possibly) informative message.