Testing
ErrorsCollection

Testing

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($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.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 %*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 environmental 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($variable, $role, $description?)

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.