Testing
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($meta, :$relaxed-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($meta, :$relaxed-name), "name has a hyphen rather than '::'" \
or diag "\nTo use hyphen in name, pass :relaxed-name to check-name\n";
Writing tests
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 Rakudo
use lib 'lib';
plan $num-tests;
# .... tests
done-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.
Thread safety
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.
Running tests
Tests can be run individually by specifying the test filename on the command line:
$ raku t/test-filename.rakutest
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
its page.
To abort the test suite upon first failure, set the
RAKU_TEST_DIE_ON_FAIL
environment variable:
$ RAKU_TEST_DIE_ON_FAIL=1 raku t/test-filename.rakutest
The same variable can be used within the test file. Set it before loading the Test module:
BEGIN %*ENV<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
environment variable:
$ env RAKU_TEST_TIMES=1 raku -e 'use Test; plan 1; pass sleep(1);'
1..1
# between two timestamps 0 microseconds
ok 1 -
# t=1000721
The same variable can be used within the test file. Set it before loading the Test module:
BEGIN %*ENV<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.
Test plans
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.
Testing return values
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.
By string comparison
is and isnt test for equality using the proper operator, depending on the object (or class) it's handled.
By approximate numeric comparison
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.
By structural comparison
Structures can be also compared using is-deeply, which will check that internal structures of the objects compared is the same.
By arbitrary comparison
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.
By object type
isa-ok tests whether an object is of a certain type.
By method name
can-ok is used on objects to check whether they have that particular method.
By role
does-ok checks whether the given variable can do a certain Role.
By regex
like and unlike check using regular expressions; in the first case passes if a match exists, in the second case when it does not.
Testing modules
Modules are tentatively loaded with use-ok, which fails if they fail to load.
Testing exceptions
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.
Grouping tests
The result of a group of subtests is only ok
if all subtests are ok
; they
are grouped using subtest.
Skipping tests
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.
Manual control
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.